# scriptCTF 2025 Writeup
>By: TAS.ElPulga
## Crypto
### Secure-Server
@ansobad x @daq

Được cho 1 file python `server.py` và 1 file pcap `capture.pcap`
File `server.py`:
```pyth=
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!")
```
Server thực hiện các bước XOR như sau:
+ Client → Server: gửi `enc = secret ⊕ key_you` (hex).
+ Server → Client: chọn random `key_srv` và trả về
`enc2 = enc ⊕ key_srv = secret ⊕ key_you ⊕ key_srv` (hex).
+ Client → Server: tính `dec = enc2 ⊕ key_you = secret ⊕ key_srv` và trả lại. (hex).
+ Server: lấy được `secret = dec ⊕ key_srv` và in ra màn hình “Secret received!”.
Điểm yếu: XOR là phép toán tuyến tính, giao thức để lộ cả ba giá trị l iên quan: `enc`, `enc2`, `dec`.
Khi XOR cả ba, ta loại bỏ được cả hai key `key_you` và `key_srv`, thu được `secret`:
$secret = enc ⊕ enc2 ⊕ dec$
Vì vậy, mặc dù plain text không bao giờ được gửi đi, nhưng nó vẫn có thể được suy ra một cách dễ dàng.
Mở file `capture.pcap` bằng Wireshark, chọn filter `tcp.port == 1337 && tcp.len > 0`
Chọn 1 packet và Follow → TCP Stream, sẽ ra được như sau

Sử dụng script sau để XOR
```python=
from binascii import unhexlify
enc = unhexlify("151e71ce4addf692d5bac83bb87911a20c39b71da3fa5e7ff05a2b2b0a83ba03")
enc2 = unhexlify("e1930164280e44386b389f7e3bc02b707188ea70d9617e3ced989f15d8a10d70")
dec = unhexlify("87ee02c312a7f1fef8f92f75f1e60ba122df321925e8132068b0871ff303960e")
flag = bytes(a ^ b ^ c for a,b,c in zip(enc, enc2, dec))
print(flag.decode())
```
Sau khi chạy script thì ra được flag

```
FLAG: scriptCTF{x0r_1s_not_s3cur3!!!!}
```
### RSA-1
@ansobad

#### Đề bài
```python
n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965
n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284
n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630
e = 3
```
- Ta được cho **3 modulus** $n_1, n_2, n_3$, cùng **công khai số mũ** $e = 3$, và **3 bản mã** $c_1, c_2, c_3$.
- Khi nhiều người dùng RSA với cùng số mũ nhỏ (e = 3) và cùng nhận một thông điệp giống nhau, thì kẻ tấn công có thể ghép các bản mã lại để khôi phục plaintext.
--> Đây chính là Hastad’s Broadcast Attack
#### Bài làm
Từ dữ kiện, ta phải làm 3 việc:
1. **Kết hợp 3 bản mã bằng CRT** (Chinese Remainder Theorem):
- Tạo ra một số $M$ sao cho:
$$
M \equiv c_1 \pmod{n_1}, \quad
M \equiv c_2 \pmod{n_2}, \quad
M \equiv c_3 \pmod{n_3}
$$
- Khi đó $M$ chính là $m^3$.
2. **Tính căn bậc ba nguyên** của $M$:
$$
m = \sqrt[3]{M}
$$
3. **Chuyển $m$ về dạng bytes** → đọc ra chuỗi → loại bỏ padding → thu được **flag**.
#### Code
```py=
from math import gcd
n1 = 156503881374173899106040027210320626006530930815116631795516553916547375688556673985142242828597628615920973708595994675661662789752600109906259326160805121029243681236938272723595463141696217880136400102526509149966767717309801293569923237158596968679754520209177602882862180528522927242280121868961697240587
c1 = 77845730447898247683281609913423107803974192483879771538601656664815266655476695261695401337124553851404038028413156487834500306455909128563474382527072827288203275942719998719612346322196694263967769165807133288612193509523277795556658877046100866328789163922952483990512216199556692553605487824176112568965
n2 = 81176790394812943895417667822424503891538103661290067749746811244149927293880771403600643202454602366489650358459283710738177024118857784526124643798095463427793912529729517724613501628957072457149015941596656959113353794192041220905793823162933257702459236541137457227898063370534472564804125139395000655909
c2 = 40787486105407063933087059717827107329565540104154871338902977389136976706405321232356479461501507502072366720712449240185342528262578445532244098369654742284814175079411915848114327880144883620517336793165329893295685773515696260299308407612535992098605156822281687718904414533480149775329948085800726089284
n3 = 140612513823906625290578950857303904693579488575072876654320011261621692347864140784716666929156719735696270348892475443744858844360080415632704363751274666498790051438616664967359811895773995052063222050631573888071188619609300034534118393135291537302821893141204544943440866238800133993600817014789308510399
c3 = 100744134973371882529524399965586539315832009564780881084353677824875367744381226140488591354751113977457961062275480984708865578896869353244823264759044617432862876208706282555040444253921290103354489356742706959370396360754029015494871561563778937571686573716714202098622688982817598258563381656498389039630
e = 3
assert gcd(n1,n2)==1 and gcd(n1,n3)==1 and gcd(n2,n3)==1
N = n1*n2*n3
def inv(a, m): return pow(a, -1, m)
M = 0
for (ci, ni) in [(c1,n1),(c2,n2),(c3,n3)]:
Ni = N // ni
M = (M + ci * Ni * inv(Ni % ni, ni)) % N
def iroot3(x):
lo, hi = 0, 1
while hi**3 <= x: hi <<= 1
while lo < hi:
mid = (lo+hi)//2
if mid**3 <= x: lo = mid+1
else: hi = mid
return lo-1
m = iroot3(M)
assert m**3 == M
pt = m.to_bytes((m.bit_length()+7)//8, 'big')
flag = pt.rstrip(b'\x12').decode()
print(flag)
```
```
Flag: scriptCTF{y0u_f0und_mr_yu's_s3cr3t_m3g_12a4e4}
```
### Secure-Server-2

#### Đề bài:
`johndoe.py`
```py=
from Crypto.Cipher import AES
k1 = b'AA' # Obviously not the actual key
k2 = b'AA' # Obviously not the actual key
message = b'scriptCTF{testtesttesttesttest!_' # Obviously not the actual flag
keys = [k1,k2]
final_keys = []
for key in keys:
assert len(key) == 2 # 2 byte key into binary
final_keys.append(bin(key[0])[2:].zfill(8)+bin(key[1])[2:].zfill(8))
cipher = AES.new(final_keys[0].encode(), mode=AES.MODE_ECB)
cipher2 = AES.new(final_keys[1].encode(), mode=AES.MODE_ECB)
enc2 = cipher2.encrypt(cipher.encrypt(message)).hex()
print(enc2)
to_dec = bytes.fromhex(input("Dec: ").strip())
secret = cipher.decrypt(cipher2.decrypt(to_dec))
print(secret.hex())
```
`server.py`
```py=
import os
from Crypto.Cipher import AES
print("With the Secure Server 2, sharing secrets is safer than ever! We now support double encryption with AES!")
enc = bytes.fromhex(input("Enter the secret, encrypted twice with your keys (in hex): ").strip())
# Our proprietary key generation method, used by the server and John Doe himself!
k3 = b'BB' # Obviously not the actual key
k4 = b'B}' # Obviously not the actual key
# flag = secret_message + k1 + k2 + k3 + k4 (where each key is 2 bytes)
# In this case: scriptCTF{testtesttesttesttest!_AAAABBB}
keys = [k3,k4]
final_keys = []
for key in keys:
assert len(key) == 2 # 2 byte key into binary
final_keys.append(bin(key[0])[2:].zfill(8)+bin(key[1])[2:].zfill(8))
cipher = AES.new(final_keys[0].encode(), mode=AES.MODE_ECB)
cipher2 = AES.new(final_keys[1].encode(), mode=AES.MODE_ECB)
enc2 = cipher2.encrypt(cipher.encrypt(enc)).hex()
print(f"Quadriple encrypted secret (in hex): {enc2}")
dec = bytes.fromhex(input("Decrypt the above with your keys again (in hex): ").strip())
secret = cipher.decrypt(cipher2.decrypt(dec))
print("Secret received!")
```
`capture.pcap`
```py
enc = 19574ac010cc9866e733adc616065e6c019d85dd0b46e5c2190c31209fc57727
enc2 = 0239bcea627d0ff4285a9e114b660ec0e97f65042a8ad209c35a091319541837
dec = 4b3d1613610143db984be05ef6f37b31790ad420d28e562ad105c7992882ff34
```
- Mở `server.py`: xem cơ chế server hoạt động (AES-ECB, double encryption, 2-byte key → 16 ký tự nhị phân).
- Mở `johndoe.py`: script mẫu chỉ ra cách tạo key và encrypt/decrypt.
-->`flag = secret_message + k1 + k2 + k3 + k4`
Dùng Wireshark để **“Follow TCP Stream”** giữa client --> server (port 1337).
Trích ra được **3 payload quan trọng**(như trên kia đã nêu)
#### Bài làm
Dùng **Meet-in-the-Middle (MITM)** 2 lần (mỗi lần ~ $2 \times 2^{16}$ phép AES):
>Brute-force trực tiếp cần $2^{32} \approx 4$ tỷ phép.
>MITM chỉ cần khoảng $2 \times 2^{16} = 131k$ phép, rất nhanh.
1. **Tìm (k3, k4)** từ cặp `enc → enc2`:
- Lập bảng: với mọi $k_3$, tính $Z = E_{k_3}(\text{enc}) \;\to\;$ map $Z \mapsto k_3$.
- Với mọi $k_4$, tính $Y = D_{k_4}(\text{enc2})$, tra $Y$ trong bảng $\;\to\;$ ra $(k_3, k_4)$
2. **Tìm (k1, k2)** từ cặp `dec → enc2`:
- Lập bảng: với mọi $k_1$, tính $U = E_{k_1}(\text{dec}) \;\to\;$ map $U \mapsto k_1$.
- Với mọi $k_2$, tính $V = D_{k_2}(\text{enc2})$, tra $V$ trong bảng $\;\to\;$ ra $(k_1, k_2)$
**Kết quả khóa:**
- $k_3 =$ `"f8"`, $k_4 =$ `"d1"`
- $k_1 =$ `"e4"`, $k_2 =$ `"b3"`
**Khôi phục** `secret_message`
Biết
$$
\text{enc} = E_{k2}(E_{k1}(\text{secret}))
\;\;\;\Rightarrow\;\;\;
\text{secret} = D_{k1}(D_{k2}(\text{enc}))
$$
--> `secret_message = "scriptCTF{s3cr37_m3ss4g3_1337!_7"
`
**Ráp flag** -->
```
flag = secret_message + k1 + k2 + k3 + k4
= scriptCTF{s3cr37_m3ss4g3_1337!_7e4b3f8d}
```
>- Entropy mỗi khóa chỉ **16 bit** → double encryption dễ bị MITM ($\approx 2 \times 2^{16}$), thay vì $2^{32}$
>- Dùng **ECB** và **không có xác thực** (không MAC/tag).
>- Giao thức “hé lộ” hai cặp P/C đủ để MITM cả hai phía.
#### Code
```py=
from Crypto.Cipher import AES
enc = bytes.fromhex("19574ac010cc9866e733adc616065e6c019d85dd0b46e5c2190c31209fc57727")
enc2 = bytes.fromhex("0239bcea627d0ff4285a9e114b660ec0e97f65042a8ad209c35a091319541837")
dec = bytes.fromhex("4b3d1613610143db984be05ef6f37b31790ad420d28e562ad105c7992882ff34")
def kbytes(two): # 2 byte -> 16 ký tự ‘0/1’
return (bin(two[0])[2:].zfill(8)+bin(two[1])[2:].zfill(8)).encode()
def E(k, m): return AES.new(kbytes(k), AES.MODE_ECB).encrypt(m)
def D(k, c): return AES.new(kbytes(k), AES.MODE_ECB).decrypt(c)
# MITM #1: tìm (k3,k4) từ enc -> enc2
mid = {}
for a in range(256):
for b in range(256):
k3 = bytes([a,b])
mid[E(k3, enc)] = k3
k3 = k4 = None
for a in range(256):
for b in range(256):
k4_try = bytes([a,b])
x = D(k4_try, enc2)
if x in mid:
k3, k4 = mid[x], k4_try
break
if k3: break
# MITM #2: tìm (k1,k2) từ dec -> enc2
mid2 = {}
for a in range(256):
for b in range(256):
k1_try = bytes([a,b])
mid2[E(k1_try, dec)] = k1_try
k1 = k2 = None
for a in range(256):
for b in range(256):
k2_try = bytes([a,b])
x = D(k2_try, enc2)
if x in mid2:
k1, k2 = mid2[x], k2_try
break
if k1: break
secret_message = D(k1, D(k2, enc)).decode('latin1')
flag = secret_message + k1.decode() + k2.decode() + k3.decode() + k4.decode()
print("k1,k2,k3,k4 =", k1, k2, k3, k4)
print("secret_message =", secret_message)
print("FLAG =", flag)
```
```
Flag: scriptCTF{s3cr37_m3ss4g3_1337!_7e4b3f8d}
```
### Eaas
@ansobad

#### Đề bài:
`server.py`
```py=
# (toàn bộ code của server.py)
from Crypto.Cipher import AES
import os
import sys
def pad(data):
padlen = 16 - (len(data) % 16)
return data + bytes([padlen]) * padlen
def unpad(data):
return data[:-data[-1]]
KEY = os.urandom(16)
IV = os.urandom(16)
def encrypt(data):
cipher = AES.new(KEY, AES.MODE_CBC, IV)
return cipher.encrypt(pad(data))
def decrypt(data):
cipher = AES.new(KEY, AES.MODE_CBC, IV)
return unpad(cipher.decrypt(data))
def main():
print("Welcome to Email as a Service!")
email = os.urandom(8).hex() + "@notscript.sorcerer"
print(f"Your Email is: {email}\n")
password = bytes.fromhex(input("Enter secure password (in hex): "))
if len(password) % 16 != 0:
print("Password must be multiple of 16 bytes!")
sys.exit(0)
if email.encode() in password or b"@script.sorcerer" in password:
print("Invalid password!")
sys.exit(0)
ciphertext = encrypt(password)
print(f"Please use this key for future login: {ciphertext.hex()}")
inbox = []
while True:
print("\nMenu:\n1. Check inbox\n2. Send email\n3. Exit")
choice = input("Enter your choice: ")
if choice == "1":
if not inbox:
print("Inbox empty!")
else:
for mail in inbox:
print("New email!")
print("From:", mail["from"])
print("Body:", mail["body"])
elif choice == "2":
enc_email = bytes.fromhex(input("Enter encrypted email (in hex): "))
try:
dec = decrypt(enc_email)
except:
print("Invalid ciphertext!")
continue
has_flag = False
recipients = []
if b"," in dec:
for part in dec.split(b","):
if part == email.encode():
has_flag = True
recipients.append(part)
else:
for part in dec:
if part == email.encode():
has_flag = True
if dec[-16:] != b"@script.sorcerer":
print("Invalid email domain!")
continue
if not has_flag:
print("Recipient not found!")
continue
inbox.append({"from": b"scriptsorcerers@script.sorcerer".decode(),
"body": open("flag.txt").read().strip()})
print("Email sent!")
else:
break
if __name__ == "__main__":
main()
```
``flag.txt``
```python
scriptCTF{REDACTED_WHEN_OFFLINE}
```
Kết nối: `nc play.scriptsorcerers.xyz 10104`
Khi kết nối tới server
- Nó cấp phát một **email** dạng `xxxxx@notscript.sorcerer`.
- Hỏi ta nhập **mật khẩu (hex)**.
→ Server sẽ mã hóa mật khẩu bằng **AES-CBC** với key và IV cố định.
→ Trả về ciphertext.
- Sau đó, ta có thể:
1. Check inbox
2. Submit email (dạng ciphertext)
Điều kiện để gửi email thành công (xem code `server.py`)
1. Trong danh sách **recipients** (phân tách bằng dấu `,`) **phải có chính xác email của mình**.
2. **16 byte cuối cùng** của plaintext phải bằng `@script.sorcerer`.
#### Ý tưởng
Phân tích AES-CBC trong server
- Server dùng **cùng một key/IV** cho cả:
- Encrypt mật khẩu (ta chọn plaintext → server cho ciphertext).
- Decrypt email (ta đưa ciphertext → server giải mã).
- Đây là một dạng **chosen-ciphertext attack** với AES-CBC.
Tính chất AES-CBC
Công thức giải mã:
$$
P_i = D_K(C_i) \oplus C_{i-1}
$$
Nếu thay đổi block trước ($C_{i-1} \to C_{i-1} \oplus \Delta$), thì plaintext kế tiếp biến đổi thành $P_i \oplus \Delta$.
Nói chung ta cần:
- Ta chọn **plaintext** dễ dàng
- Server trả về **ciphertext**
- Ta sửa **ciphertext** để khi decrypt thành **email hợp lệ** → bypass blacklist
#### Bài làm
Ta cần plaintext sau khi decrypt có format:
- `P1'`: junk (không quan trọng).
- `P2'`: `,` + 15 byte đầu email (mở đầu recipient list với email thật).
- `P3'`: phần còn lại email + `,X` (kết thúc chính xác email).
- `P4'`: junk.
- `P5'`: `@script.sorcerer` (để pass domain check).
Cách thực hiện:
- Input ban đầu chọn 5 block A, B, (phần sau email + ,X), C, D.
- Nhận ciphertext C1..C5.
- Chỉnh:
- `C1' = C1 ⊕ (P2 ⊕ Q2)` để biến P2 → Q2.
- `C4' = C4 ⊕ (P5 ⊕ Q5)` để biến P5 → Q5.
- Submit ciphertext mới.
#### Code
```py=
from pwn import remote
BS = 16
def bxor(a, b): return bytes(x ^ y for x, y in zip(a, b))
HOST, PORT = "play.scriptsorcerers.xyz", 10104
r = remote(HOST, PORT)
# Nhận email
r.recvuntil(b"Your Email is: ")
email = r.recvline().strip().decode()
print("Email:", email)
# Tạo password input (5 block)
P1 = b"A"*BS
P2 = b"B"*BS
P3 = email[15:].encode() + b"," + b"X"
P4 = b"C"*BS
P5 = b"D"*BS
P = P1 + P2 + P3 + P4 + P5
r.recvuntil(b"(in hex): ")
r.sendline(P.hex().encode())
# Nhận ciphertext
c_hex = r.recvline().decode().split(":")[1].strip()
C = bytes.fromhex(c_hex)
C1, C2, C3, C4, C5 = [C[i*BS:(i+1)*BS] for i in range(5)]
# Target block
Q2 = b"," + email[:15].encode()
Q5 = b"@script.sorcerer"
# Chỉnh block
C1p = bxor(C1, bxor(P2, Q2))
C4p = bxor(C4, bxor(P5, Q5))
crafted = C1p + C2 + C3 + C4p + C5
# Gửi email crafted
r.recvuntil(b"choice: ")
r.sendline(b"2")
r.recvuntil(b"(in hex): ")
r.sendline(crafted.hex().encode())
r.recvline()
# Check inbox
r.recvuntil(b"choice: ")
r.sendline(b"1")
r.recvuntil(b"Body: ")
flag = r.recvline().decode().strip()
print("Flag:", flag)
```
```
Flag: scriptCTF{CBC_1s_s3cur3_r1ght?_9b4dfcb09068}
```
### Mod
@ansobad

#### Đề bài
```py=
#!/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!")
```
Kết nối: `nc play.scriptsorcerers.xyz 10339`
- `secret` là số 256-bit (lớn, không thể brute force).
- Chương trình cho ta quyền nhập `num`, và in ra `num % secret`.
- Điểm yếu: Python định nghĩa toán tử `%` sao cho kết quả luôn ≥ 0.
→ Với bất kỳ số nguyên dương `m`:
`(-1) % m = m - 1`
- Vậy nếu nhập `-1`, chương trình sẽ in `secret - 1`.
- Từ đó dễ dàng suy ra `secret = output + 1`.
#### Bài làm
```py=
from pwn import *
host, port = "play.scriptsorcerers.xyz", 10339
io = remote(host, port)
io.recvuntil(b"Provide a number:")
io.sendline(b"-1")
r = int(io.recvline().strip().split()[-1])
secret = r + 1
io.recvuntil(b"Guess:")
io.sendline(str(secret).encode())
print(io.recvall().decode())
```
```
Flag: scriptCTF{-1_f0r_7h3_w1n_4a3f7db1_97948d314959}
```
## Forensics
@hphuc204
### Diskchal

Khi binwalk file ta được một file tên là ```flag.txt```

Sau đó thử xem trong đó có gì

```
FLAG: scriptCTF{1_l0v3_m461c_7r1ck5}
```
### pdf

Sau khi dùng các tools mà không nghiên cứu được gì vậy ta sẽ thử viết một đoạn script scan toàn bộ các stream trong PDF
```python=
import re, zlib
data = open("challenge.pdf","rb").read()
for i, m in enumerate(re.finditer(rb"stream\r?\n", data)):
s = m.end()
endm = re.search(rb"\r?\nendstream", data[s:])
if not endm: continue
raw = data[s:s+endm.start()]
for wbits in (15, -15): # zlib header or raw deflate
try:
txt = zlib.decompress(raw, wbits).decode("utf-8", "ignore")
if "scriptCTF{" in txt:
print(i, txt)
except Exception:
pass
```
Output sẽ hiện ra 2 flag là
```
scriptCTF{pdf_s7r34m5_0v3r_7w17ch_5tr34ms}
scriptCTF{this_is_def_the_flag_trust}
```
Nhưng ```scriptCTF{this_is_def_the_flag_trust}``` là fake flag
```
FLAG : scriptCTF{pdf_s7r34m5_0v3r_7w17ch_5tr34ms}
```
### Just Some Avocado

Sau khi binwalk ra được

Ta sẽ thấy 2 file bị encrypted là ```staticnoise.wav``` và ```justsomezip.zip```
Sau đó ta sẽ crack pass bằng ```zip2john``` và ```rockyou``` để xem trong ```staticnoise.wav```
Sử dụng audacity sẽ thấy rõ

Nó là d41v3ron hoặc d41v3ran
Ta sẽ sử dụng cái này để làm password cho ```justsomezip.zip```

```
FLAG : scriptCTF{1_l0ve_d41_v3r0n}
```
## Web
### Renderer
@daq

URL: `http://play.scriptsorcerers.xyz:<port>/`
Ở trong code đã cho, thấy đoạn code như sau
```python=
@app.route('/developer')
def developer():
cookie = request.cookies.get("developer_secret_cookie")
correct = open('./static/uploads/secrets/secret_cookie.txt').read()
if correct == '':
c = open('./static/uploads/secrets/secret_cookie.txt','w')
c.write(sha256(os.urandom(16)).hexdigest())
c.close()
correct = open('./static/uploads/secrets/secret_cookie.txt').read()
if cookie == correct:
c = open('./static/uploads/secrets/secret_cookie.txt','w')
c.write(sha256(os.urandom(16)).hexdigest())
c.close()
return f"Welcome! There is currently 1 unread message: {open('flag.txt').read()}"
else:
return "You are not a developer!"
```
Nếu truy cập `/developer` ngay lập tức, nó sẽ chỉ bảo mình không phải developer

Cần phải gửi cùng với cookie khớp với secret cookie
Chỉ cần truy cập path đã cho trong code thì tìm được secret cookie đó, path là `/static/uploads/secrets/secret_cookie.txt`

Tiếp theo chỉ cần gửi request đến `/developer` với secret cookie đó thì sẽ nhận được flag

```
FLAG: scriptCTF{my_c00k135_4r3_n0t_s4f3!_3da49da56983}
```
## OSINT
### The Insider
@daq

Vì description bảo là trong đội support có người leak thông tin, vào mục support xem có những ai

Thấy có NoobMaster, vào profile của người này trên discord thì sẽ thấy được flag

```
FLAG: scriptCTF{1ts_0bv10u5ly_j0hn_d03_aka_n00bm4573r}
```
### The Insider 2
@daq

Khi kéo tiếp xuống cuối bio của NoobMaster, thấy được link github của họ và một website

Truy cập github với đường link `https://github.com/NoobMaster9999`, thấy có repo tên là creds, là thông tin đăng nhập

Mở ra thì thấy thông tin đăng nhập với username và password đều là `scriptCTF2026`

Quay lại với trang web còn lại ở bio của họ, truy cập trang web đó

Ấn vào link dẫn tiếp đến một trang web login

Nhập thông tin đăng nhập vừa tìm được là `scriptCT2026:scriptCTF2026`
Sau đó nhận được flag

```
FLAG: scriptCTF{scriptCTF_2026_leaked?!!}
```
### The Insider 3
@daq

Tiếp tục ở trang github đó, thấy có repo `scriptCTF26`, thử vào đó

Thấy folder `OSINT` và folder `Leaked`, vào đó và thấy được flag

```
FLAG: scriptCTF{2026_fl4g_f0und_1n_2025}
```
### The Insider 4
@daq

Tiếp tục ở folder `.insider-4` trong repo github đó, thấy được mục tiêu cần tìm cùng với đó là 2 ảnh `fireworks.jpg` và `room.jpg`



Ngoài ra còn có file `.secret`, thử đọc nó và thấy được như sau

Có nhắc đến comments, thử dùng exiftool lên cả hai ảnh, thấy trên ảnh `fireworks.jpg` có comment như sau

Khi tra cả Wendell family và fireworks trên google thì ra được địa điểm là ở Rockport, Texas, Hoa Kì.

Khi nhìn lại trên ảnh `room.jpg`, thấy có 2 tòa nhà khá đặc trưng ở xa xa

Trên google map tìm được 2 tòa nhà đó, tên là `Rockport Beach Park Pavilions`, đối chiếu sang đường bên kia thì tìm được khách sạn là `Days Inn by Wyndham Rockport Texas`, mọi thứ trùng khớp so với ảnh đã cho, từ thiết kế tòa nhà đến khung cảnh xung quanh


Địa chỉ khách sạn là `901 Highway 35 N, Rockport, TX, 78382`
Có thể thấy ảnh được chụp từ tầng một, và format số phòng của chuỗi khách sạn Days Inn là `1xx`
Mà vị trí chụp ảnh lại ở giữa giữa tòa nhà, sau một vài lần thử submit flag với các số phòng khác nhau thì tìm ra số phòng là 111

```
FLAG: scriptCTF{901_Hwy_35_N_111}
```
## Misc
### Div
@daq

Có code python từ đề bài như sau
```python=
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...')
```
Có `secret` là một số nguyên dương 128 bit ngẫu nhiên.
Ngoài ra còn bị thoátra nếu có kí tự `e`/`E` trong input hoặc input dài quá 10 kí tự.
Có `div = secret / fl_num` và in ra flag nếu `div == 0`
Vì không thể lấy một số `secret` nào chia cho một số `fl_num` ≠ 0 mà ra kết quả là `0` được (luôn là `secret / fl_num != 0`)
Cần phải exploit giá trị đặc biệt mà module này cho phép
+ `decimal.Decimal` chấp nhận các giá trị như `Infinity`, `Inf`, và `-Infinity`
+ Theo IEEE-754, `finite ÷ Infinity = 0`, tương đương với `0` trong Python.
Các filter `e` chặn các input như `1e5000` nhưng không chặn `Infinity` và `Infinity` có độ dài là 8, cũng không bị block
Vì vậy, chỉ cần nhập `Infinity` thì sẽ có được flag

```
FLAG: scriptCTF{70_1nf1n17y_4nd_b3y0nd_71a333f28038}
```
### emoji
@daq

Có file `out.txt`

Mỗi Unicode code point của emoji nằm trong block xung quanh `0x1F000`.
Trừ `0x1F000` khỏi code point của mỗi ký tự để có được một byte ASCII, sau đó concatenate để khôi phục dưới dạng plain text.
Sử dụng script python sau
```python=
with open("out.txt", "r", encoding="utf-8") as f:
s = f.read().strip()
print(''.join(chr(ord(ch) - 0x1F000) for ch in s))
```

```
FLAG: scriptCTF{3m0j1_3nc0d1ng_1s_w31rd_4nd_fun!1e46d}
```
### Enchant
@daq

Có file `enc.txt`

Những kí tự đó là ngôn ngữ của Enchanting Table trong Minecraft, hay còn được gọi là Standard Galactic Alphabet (SGA)
Sử dụng trang web này để dịch ra tiếng Anh
https://lingojam.com/StandardGalacticAlphabet

```
FLAG: scriptCTF{Minecraftisfun}
```
### Div 2
@daq

Có file `chall.py`
```python=
import secrets
import decimal
decimal.getcontext().prec = 50
secret = secrets.randbelow(1 << 127) + (1 << 127) # Choose a 128 bit number
for _ in range(1000):
print("[1] Provide a number\n[2] Guess the secret number")
choice = int(input("Choice: "))
if choice == 1:
num = input('Enter a number: ')
fl_num = decimal.Decimal(num)
assert int(fl_num).bit_length() == secret.bit_length()
div = secret / fl_num
print(int(div))
if choice == 2:
guess = int(input("Enter secret number: "))
if guess == secret:
print(open('flag.txt').read().strip())
else:
print("Incorrect!")
exit(0)
```
Phải một số bí mật S có 128 bit và luôn đảm bảo bit cao nhất bằng 1, tức là: `S ∈ [2^127, 2^128 − 1]`
Trong 1000 lượt, phải chọn 1 số `num`, chương trình in ra `int(S / num)`, nhập số đoán, nếu đúng thì in ra flag
Dòng `assert int(fl_num).bit_length() == secret.bit_length()` buộc mình phải nhập số nguyên có 128 bit, là nằm trong khoảng `[2^127, 2^128 − 1]`
Giá trị in ra là `int(S / num)`:
+ Nếu `num > S` → `0 ≤ S/num` < 1 ⇒ in ra 0.
+ Nếu `num ≤ S` → `1 ≤ S/num` < 2 ⇒ in ra 1.
Như vậy, kết quả chỉ có thể là 0 hoặc 1.
Cách exploit:
+ Bắt đầu với khoảng ban đầu: `lo = 2^127`, `hi = 2^128 − 1`
+ Lặp:
+ Chọn `mid = (lo + hi + 1) // 2` (mid trên để tránh vòng lặp vô hạn)
+ Gửi `mid` vào tùy chọn 1.
+ Nếu in ra 1 ⇒ `mid ≤ S` ⇒ cập nhật `lo = mi`d còn nếu in ra 0 ⇒ m`id > S` ⇒ cập nhật `hi = mid − 1`
+ Sau tối đa 127 bước, `lo == hi == S`
+ Cuối cùng chọn mục 2, nhập số tìm được để lấy flag.
Script để solve:
```python=
from pwn import *
HOST = "play.scriptsorcerers.xyz"
PORT = 10236
LOW = 1 << 127
HIGH = (1 << 128) - 1
def query(io, n: int) -> int:
# Ask to provide a number
io.sendlineafter(b"Choice: ", b"1")
io.sendlineafter(b"Enter a number: ", str(n).encode())
# The service prints a single line: 0 or 1
line = io.recvline().strip()
return int(line)
def solve():
io = remote(HOST, PORT)
lo, hi = LOW, HIGH
while lo < hi:
mid = (lo + hi + 1) // 2
out = query(io, mid) # 0 if mid > S, 1 if mid <= S
if out == 1:
lo = mid
else:
hi = mid - 1
secret = lo
# Guess
io.sendlineafter(b"Choice: ", b"2")
io.sendlineafter(b"Enter secret number: ", str(secret).encode())
# Receive the flag line
print(io.recvlineS().strip())
if __name__ == "__main__":
solve()
```
Sau khi chạy script ra được flag

```
FLAG: scriptCTF{b1n4ry_s34rch_u51ng_d1v1s10n?!!_80cbdb6d1420}
```
### Subtract
@hphuc204

Sau khi giải nén ta được 1 file duy nhất là ```coordinates.txt```

Xem thử vài dòng đầu có gì

Có thể thấy file là danh sách toạ độ dạng (x, y) nên ta sẽ viết một đoạn script để tìm được ảnh gốc
```python=
# render_subtract.py
# Usage:
# python3 render_subtract.py coords/coordinates.txt out.png --bold 2
# (tăng/giảm --bold để nét hơn/ít hơn; mặc định không lật trục)
import argparse
from collections import Counter
from PIL import Image
def load_coords(path):
pts = []
with open(path, "r") as f:
for line in f:
s = line.strip().replace("(", "").replace(")", "").replace(",", " ")
if not s:
continue
t = s.split()
if len(t) >= 2:
x, y = int(t[0]), int(t[1])
pts.append((x, y))
return pts
def render_odd(points, size=500, bold=1, flipx=False, flipy=False, upscale=3):
# giữ các điểm xuất hiện số lần lẻ
odd = [p for p, c in Counter(points).items() if c % 2 == 1]
W = H = size
U = upscale
big = Image.new("L", (W*U, H*U), 255)
px = big.load()
for (x, y) in odd:
if flipx: x = W - 1 - x
if flipy: y = H - 1 - y
if 0 <= x < W and 0 <= y < H:
cx, cy = x*U, y*U
for dx in range(-bold, bold+1):
for dy in range(-bold, bold+1):
xx, yy = cx + dx, cy + dy
if 0 <= xx < W*U and 0 <= yy < H*U:
px[xx, yy] = 0
# xuống mẫu cho mịn
try:
from PIL import Image as _I
resample = _I.Resampling.LANCZOS # Pillow >=10
except Exception:
resample = Image.LANCZOS
return big.resize((W, H), resample)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("input", help="đường dẫn coordinates.txt")
ap.add_argument("output", help="file ảnh output (png)")
ap.add_argument("--size", type=int, default=500, help="kích thước ảnh vuông")
ap.add_argument("--bold", type=int, default=1, help="độ đậm nét (1..3)")
ap.add_argument("--flipx", action="store_true", help="lật ngang (nếu cần)")
ap.add_argument("--flipy", action="store_true", help="lật dọc (nếu cần)")
args = ap.parse_args()
pts = load_coords(args.input)
img = render_odd(pts, size=args.size, bold=args.bold, flipx=args.flipx, flipy=args.flipy)
img.save(args.output)
if __name__ == "__main__":
main()
```

Sau khi đổi sang leetcode ta sẽ thấy được flag
```
FLAG : scriptCTF{5ub7r4c7_7h3_n01s3}
```
## Programming
### Sums
@daq

Được cho file server.py như sau
```python=
#!/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())
```
Server gửi một dòng với `n=123456` số nguyên (mảng a), và chính xác n dòng query `l r`
Cần phải trả về chính xác n dòng, với mỗi dòng là `sum(a[l..r])`, và hoàn thành trong vòng 10 giây kể từ khi server bắt đầu.
Giải pháp tối ưu là tính toán một mảng tổng tiền tố một lần và trả lời mỗi truy vấn trong thời gian O(1).
Phân tích code đã cho:
+ `n = 123456`
+ `nums = [0..696969]` giá trị ngẫu nhiên → được in thành một dòng lớn
+ Sau đó n phạm vi ngẫu nhiên được in ra
+ Server xây dựng `big_input = ' '.join(nums) + "\n" + "\n".join(ranges) + "\n"`, chạy `./solve` của riêng nó và nhận được câu trả lời đúng.
+ Sau đó, server đọc `n` câu trả lời của chúng ta (mỗi câu trả lời thông qua `int(input())`), so sánh chúng với câu trả lời của chính nó, nếu không khớp thì fail
+ Nếu tổng thời gian chạy vượt quá 10 giây → fail
+ Thành công → in ra flag
Sử dụng script sau:
```python=
import socket, sys
HOST = "play.scriptsorcerers.xyz"
PORT = 10401
if len(sys.argv) >= 2: HOST = sys.argv[1]
if len(sys.argv) >= 3: PORT = int(sys.argv[2])
# Connect
sock = socket.create_connection((HOST, PORT))
rf = sock.makefile('rb', buffering=0) # reader
wf = sock.makefile('wb', buffering=0) # writer
def read_nonempty_line(reader):
while True:
line = reader.readline()
if not line:
sys.exit("Connection closed unexpectedly.")
line = line.strip()
if line:
return line
# 1) First giant line of numbers
first = read_nonempty_line(rf)
nums = list(map(int, first.split()))
n = len(nums)
# 2) Prefix sums
pref = [0] * (n + 1)
acc = 0
for i, v in enumerate(nums, 1):
acc += v
pref[i] = acc
# 3) Read n queries and compute answers
out_lines = []
append = out_lines.append
for _ in range(n):
line = read_nonempty_line(rf)
parts = line.split()
L = int(parts[0]); R = int(parts[1])
append(str(pref[R + 1] - pref[L]))
# 4) Send exactly n lines
wf.write(("\n".join(out_lines) + "\n").encode())
wf.flush()
# 5) Print whatever comes back (flag)
rest = rf.read()
if rest:
sys.stdout.buffer.write(rest)
sys.stdout.flush()
```
Sau khi chạy script có được flag

```
FLAG: scriptCTF{1_w4n7_m0r3_5um5_6c4e6a9f1459}
```
### More Divisors
@daq

Bài này không cho file, khi kết nối đến server thì chỉ nhận được rất nhiều số

Theo description, cần phải gửi lại một số nguyên duy nhất với độ dài của dãy con dài nhất có ƯCLN lớn hơn 1. Dãy con bảo toàn thứ tự nhưng không cần phải liền kề.
Một dãy con có UCLN > 1 nếu và chỉ nếu tất cả các phần tử của nó có chung ít nhất một ước nguyên tố.
Vậy dãy con dài nhất như vậy đơn giản là số lượng lớn nhất các số chia hết cho cùng một số nguyên tố.
Script python để giải:
```python=
from pwn import remote
import re
HOST = "play.scriptsorcerers.xyz"
PORT = 10394
def compute_spf(nmax: int):
"""Smallest prime factor sieve up to nmax."""
spf = list(range(nmax + 1))
spf[0] = 0
if nmax >= 1:
spf[1] = 1
i = 2
while i * i <= nmax:
if spf[i] == i: # i is prime
j = i * i
while j <= nmax:
if spf[j] == j:
spf[j] = i
j += i
i += 1
return spf
def factor_distinct(x: int, spf):
"""Return distinct prime factors of x using spf."""
facs = set()
while x > 1:
p = spf[x]
if p <= 1:
break
facs.add(p)
while x % p == 0:
x //= p
return facs
def parse_ints(raw: bytes):
# Be forgiving: grab all digit runs
return list(map(int, re.findall(rb"\d+", raw)))
def receive_all_numbers(conn, quiet_timeout=0.8, max_total_bytes=30_000_000):
"""
Slurp bytes until the stream goes quiet for `quiet_timeout` seconds.
Some CTF backends don't print a prompt; they just stop sending.
"""
buf = bytearray()
total = 0
while True:
try:
chunk = conn.recv(timeout=quiet_timeout)
except EOFError:
break
if not chunk:
# quiet period
break
buf += chunk
total += len(chunk)
if total > max_total_bytes:
break
return bytes(buf)
def solve():
r = remote(HOST, PORT)
raw = receive_all_numbers(r, quiet_timeout=0.8)
nums = parse_ints(raw)
# Filter out 0/1 (they don't help)
nums = [x for x in nums if x > 1]
if not nums:
ans = 0
r.sendline(str(ans).encode())
print(r.recvall(timeout=2).decode(errors="ignore"))
return
nmax = max(nums)
spf = compute_spf(nmax)
# Count how many numbers contain each prime factor
from collections import defaultdict
cnt = defaultdict(int)
for a in nums:
for p in factor_distinct(a, spf):
cnt[p] += 1
ans = max(cnt.values(), default=0)
# Safety: if every number was >1, ans will be at least 1 anyway.
r.sendline(str(ans).encode())
# Print whatever comes back (usually the flag)
out = r.recvall(timeout=3)
print(out.decode(errors="ignore"))
if __name__ == "__main__":
solve()
```
Sau khi chạy script sẽ ra flag

```
FLAG: scriptCTF{7H3_m0r3_f4C70r5_7h3_b3773r_4219cc5743f6}
```
### Windows To Infinity
@hphuc204

Đề bài cho đoạn code `server.py` như sau
```python=
mport random
import subprocess
n = 1000000
window_size = n / 2
"""
You will receive {n} numbers.
Every round, you will need to calculate a specific value for every window.
You will be doing the calculations on the same {n} numbers every round.
For example, in this round, you will need to find the sum of every window.
Sample testcase for Round 1 if n = 10
Input:
1 6 2 8 7 6 2 8 3 8
Output:
24 29 25 31 26 27
"""
a = []
for i in range(n):
a.append(str(random.randint(0, 100000)))
print(a[i], end=' ')
print()
proc = subprocess.Popen(['./solve'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
proc.stdin.write(' '.join(a) + '\n')
proc.stdin.flush()
def round(roundnumber, roundname):
print(f"Round {roundnumber}: {roundname}!")
ur_output = list(map(int, input().split()))
correct_output = list(map(int, proc.stdout.readline().split()))
if ur_output != correct_output:
print('uh oh')
exit(1)
round(1, "Sums")
round(2, "Xors")
round(3, "Means") # Note: means are rounded down
round(4, "Median") # Note: medians are calculated using a[floor(n / 2)]
round(5, "Modes") # Note: if there is a tie, print the mode with the largest value
round(6, "Mex (minimum excluded)") # examples: mex(5, 4, 2, 0) = 1, mex(4, 1, 2, 0) = 3, mex(5, 4, 2, 1) = 0
round(7, "# of Distinct Numbers")
round(8, "Sum of pairwise GCD") # If bounds of the window are [l, r], find the sum of gcd(a[i], a[j]) for every i, j where l <= i < j <= r,
print(open('flag.txt', 'r').readline())
```
Khi log thử vào server sẽ thấy rất nhiều con số

Dựa vào ```server.py``` sinh ra ```n=1e6``` số ngẫu nhiên trong ```[0,100000]```.
Khi in ra thành 1 dòng thì đó là input mà solver phải xử lý
Mọi round đều thao tác trên cùng dãy a, chỉ khác hà tính trên mỗi cửa sổ ```[i..i+w-1]```.
Ta phải tính nhanh: mỗi bước trượt chỉ bỏ 1 phần tử (out) và thêm 1 phần tử (in) sẽ cập nhật cấu trúc
Ta sẽ tạo 2 đoạn code là ```solver C++``` để tính 8 round sliding-window và ```python``` nối tới server để nhận dãy số, gọi solver, rồi lần lượt gửi output cho từng round để lấy flag
Code python:
```python=
from pwn import *
import subprocess
HOST = 'play.scriptsorcerers.xyz'
PORT = 10099
def main():
r = remote(HOST, PORT)
# nhận nguyên dòng chứa 1e6 số
nums_line = r.recvline(timeout=120)
# chạy solver; nó sẽ in từng round ngay khi tính xong
p = subprocess.Popen(['./solver_infinity'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
text=True, bufsize=1<<20)
p.stdin.write(nums_line.decode('ascii', 'ignore'))
p.stdin.flush()
# gửi lần lượt 8 dòng output, không đợi prompt
for _ in range(8):
ans_line = p.stdout.readline() # 1 dòng từ solver
r.sendline(ans_line.strip().encode())
# in flag
print(r.recvall(timeout=10).decode(errors='ignore'))
if __name__ == '__main__':
main()
```
Code C++:
```cpp=
// g++ -O3 -march=native -std=c++17 -pipe -o solver_infinity solve_windows_to_infinity.cpp
#include <bits/stdc++.h>
using namespace std;
static const int VMAX = 100000; // values are in [0..100000]
/*** -------- Fenwick (BIT) for median -------- ***/
struct Fenwick {
int n; vector<int> bit; int maxpow;
Fenwick(int n=0): n(n), bit(n+1,0) {
int p = 1; while ((p<<1) <= n) p<<=1; maxpow = p;
}
inline void add(int idx, int delta){ // idx: 1..n
for (; idx<=n; idx+=idx&-idx) bit[idx] += delta;
}
// find smallest idx s.t. prefix sum >= k (k is 1-based rank)
inline int kth(int k){
int idx = 0;
for (int p=maxpow; p; p>>=1){
int nxt = idx + p;
if (nxt<=n && bit[nxt] < k){
idx = nxt;
k -= bit[nxt];
}
}
return idx+1; // 1..n
}
};
/*** -------- precompute phi and divisors -------- ***/
static vector<int> phi(VMAX+1);
static vector<vector<int>> divisors(VMAX+1);
void precompute_phi_divs(){
for (int i=0;i<=VMAX;i++) phi[i]=i;
for (int p=2;p<=VMAX;p++) if (phi[p]==p){
for (int m=p;m<=VMAX;m+=p) phi[m] -= phi[m]/p;
}
for (int d=1; d<=VMAX; ++d){
for (int m=d; m<=VMAX; m+=d) divisors[m].push_back(d);
}
}
/*** -------- helpers -------- ***/
template<class T>
static inline void print_line(const vector<T>& v){
static const char sp=' ', nl='\n';
int m = (int)v.size();
for (int i=0;i<m;i++){
if (i) putchar_unlocked(sp);
long long x = (long long)v[i];
if (x==0){ putchar_unlocked('0'); continue; }
if (x<0){ putchar_unlocked('-'); x=-x; }
char buf[32]; int b=0;
while (x){ buf[b++] = char('0'+(x%10)); x/=10; }
while (b--) putchar_unlocked(buf[b]);
}
putchar_unlocked(nl);
fflush(stdout);
}
/*** -------- Round 1: sums -------- ***/
vector<long long> window_sums(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<long long> ans; ans.reserve(m);
long long cur=0;
for (int i=0;i<w;i++) cur += a[i];
ans.push_back(cur);
for (int i=0;i<m-1;i++){
cur += a[i+w] - a[i];
ans.push_back(cur);
}
return ans;
}
/*** -------- Round 2: xors -------- ***/
vector<int> window_xors(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<int> ans; ans.reserve(m);
int cur=0;
for (int i=0;i<w;i++) cur ^= a[i];
ans.push_back(cur);
for (int i=0;i<m-1;i++){
cur ^= a[i];
cur ^= a[i+w];
ans.push_back(cur);
}
return ans;
}
/*** -------- Round 3: means -------- ***/
vector<long long> window_means_from_sums(const vector<long long>& sums, int w){
int m=sums.size();
vector<long long> ans; ans.reserve(m);
for (int i=0;i<m;i++) ans.push_back(sums[i]/w);
return ans;
}
/*** -------- Round 4: medians (lower median if even) -------- ***/
vector<int> window_medians(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<int> ans; ans.reserve(m);
Fenwick fw(VMAX+1);
auto addval = [&](int v,int d){ fw.add(v+1,d); };
for (int i=0;i<w;i++) addval(a[i],+1);
int k0 = (w-1)/2; // lower median index
ans.push_back(fw.kth(k0+1)-1);
for (int i=0;i<m-1;i++){
addval(a[i],-1);
addval(a[i+w],+1);
ans.push_back(fw.kth(k0+1)-1);
}
return ans;
}
/*** -------- Round 5: modes -------- ***/
vector<int> window_modes(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<int> ans; ans.reserve(m);
vector<int> cnt(VMAX+1,0);
priority_queue<pair<int,int>> pq;
auto addv = [&](int x){ int c = ++cnt[x]; pq.push({c,x}); };
auto remv = [&](int x){ int c = --cnt[x]; pq.push({c,x}); };
auto getmode = [&](){
while(true){
auto [f,v] = pq.top();
if (cnt[v]==f) return v;
pq.pop();
}
};
for (int i=0;i<w;i++) addv(a[i]);
ans.push_back(getmode());
for (int i=0;i<m-1;i++){
remv(a[i]); addv(a[i+w]);
ans.push_back(getmode());
}
return ans;
}
/*** -------- Round 6: mex -------- ***/
vector<int> window_mex(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<int> ans; ans.reserve(m);
vector<int> cnt(VMAX+2,0);
priority_queue<int, vector<int>, greater<int>> missing;
for (int x=0;x<=VMAX+1;x++) missing.push(x);
auto fix = [&](){ while (!missing.empty() && cnt[missing.top()]>0) missing.pop(); };
auto addv = [&](int x){ ++cnt[x]; };
auto remv = [&](int x){ if (--cnt[x]==0) missing.push(x); };
for (int i=0;i<w;i++) addv(a[i]);
fix(); ans.push_back(missing.top());
for (int i=0;i<m-1;i++){
remv(a[i]); addv(a[i+w]);
fix(); ans.push_back(missing.top());
}
return ans;
}
/*** -------- Round 7: distinct -------- ***/
vector<int> window_distinct(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<int> ans; ans.reserve(m);
vector<int> cnt(VMAX+1,0);
int distinct = 0;
auto addv = [&](int x){ if (++cnt[x]==1) ++distinct; };
auto remv = [&](int x){ if (--cnt[x]==0) --distinct; };
for (int i=0;i<w;i++) addv(a[i]);
ans.push_back(distinct);
for (int i=0;i<m-1;i++){
remv(a[i]); addv(a[i+w]);
ans.push_back(distinct);
}
return ans;
}
/*** -------- Round 8: pairwise gcd -------- ***/
vector<long long> window_pairwise_gcd(const vector<int>& a, int w){
int n=a.size(), m=n-w+1;
vector<long long> ans; ans.reserve(m);
vector<int> M(VMAX+1,0);
long long pairs_sum = 0;
long long sum_all = 0;
long long zero_cnt = 0;
auto add_pos = [&](int x){
long long s=0;
for (int d: divisors[x]) s += 1LL*phi[d]*M[d];
s += zero_cnt * 1LL * x;
pairs_sum += s;
for (int d: divisors[x]) ++M[d];
sum_all += x;
};
auto add_zero = [&](){ pairs_sum += sum_all; ++zero_cnt; };
auto rem_pos = [&](int x){
long long s=0;
for (int d: divisors[x]) s += 1LL*phi[d]*(M[d]-1);
s += zero_cnt * 1LL * x;
pairs_sum -= s;
for (int d: divisors[x]) --M[d];
sum_all -= x;
};
auto rem_zero = [&](){ pairs_sum -= sum_all; --zero_cnt; };
for (int i=0;i<w;i++){ if (a[i]==0) add_zero(); else add_pos(a[i]); }
ans.push_back(pairs_sum);
for (int i=0;i<m-1;i++){
int out=a[i], in=a[i+w];
if (out==0) rem_zero(); else rem_pos(out);
if (in==0) add_zero(); else add_pos(in);
ans.push_back(pairs_sum);
}
return ans;
}
/*** ---------------- main ---------------- ***/
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
string line;
getline(cin, line);
vector<int> a; a.reserve(1000000);
int x=0; bool innum=false;
for (char c: line){
if (c>='0' && c<='9'){ x = x*10 + (c-'0'); innum=true; }
else{ if (innum){ a.push_back(x); x=0; innum=false; } }
}
if (innum) a.push_back(x);
int n=a.size(), w=n/2;
auto sums = window_sums(a,w);
print_line(sums);
auto xors = window_xors(a,w);
print_line(xors);
auto means = window_means_from_sums(sums,w);
print_line(means);
auto meds = window_medians(a,w);
print_line(meds);
auto modes = window_modes(a,w);
print_line(modes);
auto mexs = window_mex(a,w);
print_line(mexs);
auto dists = window_distinct(a,w);
print_line(dists);
precompute_phi_divs(); // chỉ gọi trước Round 8
auto gcdsum = window_pairwise_gcd(a,w);
print_line(gcdsum);
return 0;
}
```

```
FLAG : scriptCTF{i_10v3_m4_w1nd0wwwwwwww5_dd197441ce24}
```
### Back From Where
@daq

Cho file `server.py` như sau
```python=
import random
import sys
import subprocess
import time
n = 100
grid_lines = []
for _ in range(n):
row = []
for _ in range(n):
flip = random.randint(1, 2)
if flip == 1:
row.append(str(random.randint(1, 696) * 2))
else:
row.append(str(random.randint(1, 696) * 5))
grid_lines.append(' '.join(row))
for line in grid_lines:
sys.stdout.write(line + '\n')
start = int(time.time())
proc = subprocess.run(['./solve'], input='\n'.join(grid_lines).encode(), stdout=subprocess.PIPE)
ans = []
all_ans = proc.stdout.decode()
for line in all_ans.split('\n')[:100]:
ans.append(list(map(int, line.strip().split(' '))))
ur_output = []
for i in range(n):
ur_output.append(list(map(int, input().split())))
if int(time.time()) - start > 20:
print("Time Limit Exceeded!")
exit(-1)
if ur_output == ans:
with open('flag.txt', 'r') as f:
print(f.readline())
else:
print("Wrong Answer!")
```
Được cho 1 grid $100x100$ số nguyên dương. Bắt đầu ở $(0,0)$ và chỉ được di chuyển sang phải hoặc xuống dưới đến $(99,99)$
Trên bất kỳ đường đi nào, phải nhân tất cả các số đã đi qua. Đối với mỗi grid cell $(i,j)$, đưa ra số lượng tối đa các số 0 theo sau có thể đạt được bằng bất kỳ đường dẫn nào từ $(0,0)$ đến $(i,j)$
Server sẽ:
+ In ra grid $100x100$
+ Tính toán đáp án bằng cách sử dụng `./solve ` đã bị ẩn
+ Đọc lại đáp án $100x100$ của mình
+ Giới hạn thời gian 20s
+ In ra flag nếu bảng của mình khớp
Sau khi tra solution challenge [BackFromBrazil](https://github.com/n00bzUnit3d/n00bzCTF2024-Official-Writeups/tree/main/Programming/BackFromBrazil) đã được mention ở description, thấy challenge đó đã tối đa hóa tổng đường dẫn đi bên phải/ đi xuống + backtrace để trả về chuỗi đường đi ngắn nhất, xem được sự lặp lại và backtrace cốt lõi của nó
Sử dụng script python sau
```python=
import sys, socket
HOST = "play.scriptsorcerers.xyz"
PORT = 10050
def factor2_5(n: int):
c2 = 0
while n & 1 == 0:
c2 += 1
n >>= 1
c5 = 0
while n % 5 == 0:
c5 += 1
n //= 5
return c2, c5
def compute_ans(grid):
n = len(grid)
# precompute exponents of 2 and 5 for each cell
p2 = [[0]*n for _ in range(n)]
p5 = [[0]*n for _ in range(n)]
for i in range(n):
for j in range(n):
a,b = factor2_5(grid[i][j])
p2[i][j], p5[i][j] = a, b
prev_sets = [None]*n # Pareto sets for previous row
ans_lines = []
for i in range(n):
curr_sets = [None]*n
out_row = []
for j in range(n):
add2, add5 = p2[i][j], p5[i][j]
if i == 0 and j == 0:
pairs = [(add2, add5)]
else:
pairs = []
if i > 0:
pairs += [(a+add2, b+add5) for (a,b) in prev_sets[j]]
if j > 0:
pairs += [(a+add2, b+add5) for (a,b) in curr_sets[j-1]]
# Pareto reduce
pairs.sort(key=lambda t: (-t[0], -t[1]))
res, best_c5 = [], -1
for a,b in pairs:
if b > best_c5:
res.append((a,b))
best_c5 = b
curr_sets[j] = res
out_row.append(max(min(a,b) for a,b in res)) # answer for (i,j)
ans_lines.append(out_row)
prev_sets = curr_sets
return ans_lines
def remote_mode():
s = socket.create_connection((HOST, PORT))
f = s.makefile('rwb', buffering=0)
# read 100 lines
grid = [list(map(int, f.readline().strip().split())) for _ in range(100)]
# compute & send
ans = compute_ans(grid)
for row in ans:
f.write((' '.join(map(str, row)) + '\n').encode())
# print whatever remains (flag)
rest = f.read(4096)
if rest:
print(rest.decode(errors='ignore'), end='')
s.close()
def main():
remote_mode()
if __name__ == "__main__":
main()
```
Sau khi chạy script sẽ ra flag

```
FLAG: scriptCTF{d0nt_u_ju5t_!@ve_z%ro3s???_ecb390924303}
```
## Rev
@hphuc204
### Plastic Shield

Trước hết ta sẽ định dạng file và xem những header đầu

Có thể thấy không có overlay ở cuối
Ta sẽ liệt kê các hàm import từ libc xem

Nhìn imports thì không có ```strcmp``` hoặc ```memcmp```, chỉ có ```scanf```,```strtol```,```strlen```,```sprintf```,```putchar```
Xem thử xem format của ```scanf``` và dump ra để biết nó có gì

Có 2 format trong ```.rodata``` là ```"%255s"``` và ```"%02x"```
Xem các hàm xung quanh ```0x40058e``` xem
```python
plastic-shield: file format elf64-x86-64
Disassembly of section .text:
0000000000400540 <hex_to_bytes+0x6a>:
400540: 55 push rbp
400541: e0 48 loopne 40058b <main+0x2b>
400543: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
400546: 48 01 d0 add rax,rdx
400549: 89 ca mov edx,ecx
40054b: 88 10 mov BYTE PTR [rax],dl
40054d: 48 83 45 f8 01 add QWORD PTR [rbp-0x8],0x1
400552: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
400556: 48 3b 45 d8 cmp rax,QWORD PTR [rbp-0x28]
40055a: 72 98 jb 4004f4 <hex_to_bytes+0x1e>
40055c: 90 nop
40055d: 90 nop
40055e: c9 leave
40055f: c3 ret
0000000000400560 <main>:
400560: 55 push rbp
400561: 48 89 e5 mov rbp,rsp
400564: 48 81 ec 40 03 00 00 sub rsp,0x340
40056b: bf 30 33 41 00 mov edi,0x413330
400570: b8 00 00 00 00 mov eax,0x0
400575: e8 26 fe ff ff call 4003a0 <printf@plt>
40057a: 48 8d 85 c0 fe ff ff lea rax,[rbp-0x140]
400581: 48 89 c6 mov rsi,rax
400584: bf 4c 33 41 00 mov edi,0x41334c
400589: b8 00 00 00 00 mov eax,0x0
40058e: e8 3d fe ff ff call 4003d0 <__isoc99_scanf@plt>
400593: 48 8d 85 c0 fe ff ff lea rax,[rbp-0x140]
40059a: 48 89 c7 mov rdi,rax
40059d: e8 ee fd ff ff call 400390 <strlen@plt>
4005a2: 48 89 45 e0 mov QWORD PTR [rbp-0x20],rax
4005a6: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
4005aa: 48 6b c0 3c imul rax,rax,0x3c
4005ae: 48 c1 e8 02 shr rax,0x2
4005b2: 48 ba c3 f5 28 5c 8f movabs rdx,0x28f5c28f5c28f5c3
4005b9: c2 f5 28
4005bc: 48 f7 e2 mul rdx
4005bf: 48 89 d0 mov rax,rdx
4005c2: 48 c1 e8 02 shr rax,0x2
4005c6: 48 89 45 d8 mov QWORD PTR [rbp-0x28],rax
4005ca: 48 8d 95 c0 fe ff ff lea rdx,[rbp-0x140]
4005d1: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28]
4005d5: 48 01 d0 add rax,rdx
4005d8: 0f b6 00 movzx eax,BYTE PTR [rax]
4005db: 88 85 7f fe ff ff mov BYTE PTR [rbp-0x181],al
4005e1: 48 8d 95 7f fe ff ff lea rdx,[rbp-0x181]
4005e8: 48 8d 85 80 fe ff ff lea rax,[rbp-0x180]
4005ef: b9 01 00 00 00 mov ecx,0x1
4005f4: be 40 00 00 00 mov esi,0x40
4005f9: 48 89 c7 mov rdi,rax
4005fc: e8 b2 85 00 00 call 408bb3 <crypto_blake2b>
400601: 48 c7 45 f8 00 00 00 mov QWORD PTR [rbp-0x8],0x0
400608: 00
400609: eb 3f jmp 40064a <main+0xea>
40060b: 48 8d 95 80 fe ff ff lea rdx,[rbp-0x180]
400612: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
400616: 48 01 d0 add rax,rdx
400619: 0f b6 00 movzx eax,BYTE PTR [rax]
40061c: 0f b6 c0 movzx eax,al
40061f: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8]
400623: 48 8d 0c 12 lea rcx,[rdx+rdx*1]
400627: 48 8d 95 f0 fd ff ff lea rdx,[rbp-0x210]
40062e: 48 01 d1 add rcx,rdx
400631: 89 c2 mov edx,eax
400633: be 52 33 41 00 mov esi,0x413352
400638: 48 89 cf mov rdi,rcx
40063b: b8 00 00 00 00 mov eax,0x0
400640: e8 9b fd ff ff call 4003e0 <sprintf@plt>
400645: 48 83 45 f8 01 add QWORD PTR [rbp-0x8],0x1
40064a: 48 83 7d f8 3f cmp QWORD PTR [rbp-0x8],0x3f
40064f: 76 ba jbe 40060b <main+0xab>
400651: c6 85 70 fe ff ff 00 mov BYTE PTR [rbp-0x190],0x0
400658: 48 c7 45 d0 58 33 41 mov QWORD PTR [rbp-0x30],0x413358
40065f: 00
400660: 48 8d 8d d0 fd ff ff lea rcx,[rbp-0x230]
400667: 48 8d 85 f0 fd ff ff lea rax,[rbp-0x210]
40066e: ba 20 00 00 00 mov edx,0x20
400673: 48 89 ce mov rsi,rcx
400676: 48 89 c7 mov rdi,rax
400679: e8 58 fe ff ff call 4004d6 <hex_to_bytes>
40067e: 48 8d 85 c0 fd ff ff lea rax,[rbp-0x240]
400685: 48 8d 95 f0 fd ff ff lea rdx,[rbp-0x210]
40068c: 48 8d 4a 40 lea rcx,[rdx+0x40]
400690: ba 10 00 00 00 mov edx,0x10
400695: 48 89 c6 mov rsi,rax
400698: 48 89 cf mov rdi,rcx
40069b: e8 36 fe ff ff call 4004d6 <hex_to_bytes>
4006a0: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30]
4006a4: 48 89 c7 mov rdi,rax
4006a7: e8 e4 fc ff ff call 400390 <strlen@plt>
4006ac: 48 d1 e8 shr rax,1
4006af: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax
4006b3: 48 8b 45 f0 mov rax,QWORD PTR [rbp-0x10]
4006b7: 48 89 c7 mov rdi,rax
4006ba: e8 01 fd ff ff call 4003c0 <malloc@plt>
4006bf: 48 89 45 c8 mov QWORD PTR [rbp-0x38],rax
4006c3: 48 8b 55 f0 mov rdx,QWORD PTR [rbp-0x10]
4006c7: 48 8b 4d c8 mov rcx,QWORD PTR [rbp-0x38]
4006cb: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30]
4006cf: 48 89 ce mov rsi,rcx
4006d2: 48 89 c7 mov rdi,rax
4006d5: e8 fc fd ff ff call 4004d6 <hex_to_bytes>
4006da: 48 8d 95 c0 fd ff ff lea rdx,[rbp-0x240]
4006e1: 48 8d 8d d0 fd ff ff lea rcx,[rbp-0x230]
4006e8: 48 8d 85 c0 fc ff ff lea rax,[rbp-0x340]
4006ef: 48 rex.W
```
Có thể thấy:
```400584``` và ```40058e``` là chương trình nhận input chuỗi từ người dùng
```4005fc``` là hàm ```crypto_blake2b()``` băm input thành buffer 64 bytes tại ```[rbp-0x180]```, đây là nguồn tạo ```key/IV``` để giải flag
Đây là các phần cuối của ```hash (BLAKE2b)``` để tạo ```key/IV```
```
lea rax, [rbp-0x240]
lea rdx, [rbp-0x210]
lea rcx, [rdx+0x40] ; lấy từ offset +0x40 = 64 byte → chính là BLAKE2b[64:]
mov edx, 0x10 ; 16 bytes
call hex_to_bytes
```
Cuối cùng là giải mã ciphertext
```
lea rax, [rbp-0x340] ; output buffer
call hex_to_bytes ; load ciphertext
```
Đã sẵn ciphertext từ ```.rodata (0x413358)```
Sau khi setup ``key/IV`` từ hash của input thì chương trình sẽ giải mã ciphertext
Ta sẽ viết Brute-force một byte đúng tại một vị trí trong input hex để tìm flag thử xem
```python
import subprocess, sys
BIN = "./plastic-shield"
L = 10 # độ dài input; idx = floor(0.6*L) = 6
IDX = int(0.6 * L)
def run(inp: bytes) -> bytes:
p = subprocess.run([BIN], input=inp + b"\n",
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
return p.stdout
for b in range(256):
s = bytearray(b"A"*L)
s[IDX] = b
out = run(s)
# tìm chuỗi nhìn giống flag
if b"CTF{" in out or b"ScriptCTF{" in out:
print(f"[FOUND] byte=0x{b:02x}")
print(out.decode("latin1", "ignore"))
sys.exit(0)
# nếu chưa bắt được, in vài mẫu “đáng ngờ” để soi tay
print("Chưa thấy rõ flag. Thử L khác (vd 8, 12, 16) hoặc grep thêm.")
```

```
FLAG: scriptCTF{20_cau541i71e5_d3f3n5es_d0wn}
```
### ForeignDesign

Sau khi tải về ta sẽ giải nén và xem trong có gì và được ```native``` không phải thư mục mà là một file nén

Thấy có 3 mục ```linux64/libforeign.so```, ```linux32/libforeign.so```, ```win32/foreign.dll``` khá lạ
Định dạng file linux/libforeign.so

Có thể thấy ```libforeign.so``` là một thư viện native 64-bit ELF dành cho Linux/x86-64, đã bị strip symbol debug, dùng cho JNI
Liệt kê các symbol của nó ra thì thấy có 2 hàm ```JNI``` là ```Java_xyz_scriptctf_Main_initialize```và ```Java_xyz_scriptctf_Main_sc```

Ta sẽ dùng ```objdump``` để phân tích xem
```python
libforeign.so: file format elf64-x86-64
Disassembly of section .text:
00000000000013a0 <Java_xyz_scriptctf_Main_initialize@@Base+0x290>:
13a0: 8d 4e 13 lea ecx,[rsi+0x13]
13a3: 31 f9 xor ecx,edi
13a5: 8d 04 76 lea eax,[rsi+rsi*2]
13a8: 01 c8 add eax,ecx
13aa: 83 f0 5a xor eax,0x5a
13ad: c3 ret
13ae: 66 90 xchg ax,ax
00000000000013b0 <Java_xyz_scriptctf_Main_sc@@Base>:
13b0: 55 push rbp
13b1: 41 57 push r15
13b3: 41 56 push r14
13b5: 41 55 push r13
13b7: 41 54 push r12
13b9: 53 push rbx
13ba: 48 83 ec 28 sub rsp,0x28
13be: 89 cd mov ebp,ecx
13c0: 41 89 d4 mov r12d,edx
13c3: 49 89 f5 mov r13,rsi
13c6: 48 89 fb mov rbx,rdi
13c9: 48 8b 07 mov rax,QWORD PTR [rdi]
13cc: 48 8d 15 31 0c 00 00 lea rdx,[rip+0xc31] # 2004 <Java_xyz_scriptctf_Main_sc@@Base+0xc54>
13d3: 4c 8d 3d 26 0c 00 00 lea r15,[rip+0xc26] # 2000 <Java_xyz_scriptctf_Main_sc@@Base+0xc50>
13da: 4c 89 f9 mov rcx,r15
13dd: ff 90 80 04 00 00 call QWORD PTR [rax+0x480]
13e3: 49 89 c6 mov r14,rax
13e6: 48 8b 03 mov rax,QWORD PTR [rbx]
13e9: 48 8d 15 13 0c 00 00 lea rdx,[rip+0xc13] # 2003 <Java_xyz_scriptctf_Main_sc@@Base+0xc53>
13f0: 48 89 df mov rdi,rbx
13f3: 4c 89 ee mov rsi,r13
13f6: 4c 89 f9 mov rcx,r15
13f9: ff 90 80 04 00 00 call QWORD PTR [rax+0x480]
13ff: 4d 85 f6 test r14,r14
1402: 0f 84 f6 00 00 00 je 14fe <Java_xyz_scriptctf_Main_sc@@Base+0x14e>
1408: 49 89 c7 mov r15,rax
140b: 48 85 c0 test rax,rax
140e: 0f 84 ea 00 00 00 je 14fe <Java_xyz_scriptctf_Main_sc@@Base+0x14e>
1414: 48 8b 03 mov rax,QWORD PTR [rbx]
1417: 48 89 df mov rdi,rbx
141a: 4c 89 ee mov rsi,r13
141d: 4c 89 f2 mov rdx,r14
1420: ff 90 88 04 00 00 call QWORD PTR [rax+0x488]
1426: 49 89 c6 mov r14,rax
1429: 48 8b 03 mov rax,QWORD PTR [rbx]
142c: 48 89 df mov rdi,rbx
142f: 4c 89 ee mov rsi,r13
1432: 4c 89 fa mov rdx,r15
1435: ff 90 88 04 00 00 call QWORD PTR [rax+0x488]
143b: 4d 85 f6 test r14,r14
143e: 0f 84 ba 00 00 00 je 14fe <Java_xyz_scriptctf_Main_sc@@Base+0x14e>
1444: 49 89 c7 mov r15,rax
1447: 48 85 c0 test rax,rax
144a: 0f 84 ae 00 00 00 je 14fe <Java_xyz_scriptctf_Main_sc@@Base+0x14e>
1450: 48 8b 03 mov rax,QWORD PTR [rbx]
1453: 48 89 df mov rdi,rbx
1456: 4c 89 f6 mov rsi,r14
1459: ff 90 58 05 00 00 call QWORD PTR [rax+0x558]
145f: 48 8b 03 mov rax,QWORD PTR [rbx]
1462: 48 89 df mov rdi,rbx
1465: 4c 89 fe mov rsi,r15
1468: ff 90 58 05 00 00 call QWORD PTR [rax+0x558]
146e: 48 8b 03 mov rax,QWORD PTR [rbx]
1471: 48 89 df mov rdi,rbx
1474: 4c 89 f6 mov rsi,r14
1477: 31 d2 xor edx,edx
1479: ff 90 d8 05 00 00 call QWORD PTR [rax+0x5d8]
147f: 89 6c 24 0c mov DWORD PTR [rsp+0xc],ebp
1483: 44 89 e5 mov ebp,r12d
1486: 4d 89 f4 mov r12,r14
1489: 4d 89 fe mov r14,r15
148c: 49 89 c7 mov r15,rax
148f: 48 8b 03 mov rax,QWORD PTR [rbx]
1492: 48 89 df mov rdi,rbx
1495: 4c 89 f6 mov rsi,r14
1498: 31 d2 xor edx,edx
149a: ff 90 d8 05 00 00 call QWORD PTR [rax+0x5d8]
```
Có thể thấy hàm helper
```python
00000000000013a0 <Java_xyz_scriptctf_Main_initialize@@Base+0x290>:
13a0: 8d 4e 13 lea ecx,[rsi+0x13]
13a3: 31 f9 xor ecx,edi
13a5: 8d 04 76 lea eax,[rsi+rsi*2]
13a8: 01 c8 add eax,ecx
13aa: 83 f0 5a xor eax,0x5a
13ad: c3 ret
13ae: 66 90 xchg ax,ax
```
Từ đó suy ra công thức
```python
input: edi, rsi
ecx = rsi + 0x13
ecx = ecx XOR edi
eax = rsi*3 + ecx
eax = eax XOR 0x5a
return eax
```
```python
exp_even = ( 3*i + ( (i + 19) ^ ch ) ) ^ 0x5A
y = (exp_even ^ 0x5A) - 3*i
ch = (i + 19) ^ y
```
Từ hàm chính ```Java_xyz_scriptctf_Main_sc```
```python
00000000000013b0 <Java_xyz_scriptctf_Main_sc@@Base>:
13b0: push rbp
13b1: push r15
13b3: push r14
13b5: push r13
13b7: push r12
13b9: push rbx
...
148f: 48 8b 03 mov rax,QWORD PTR [rbx]
1492: 48 89 df mov rdi,rbx
1495: 4c 89 f6 mov rsi,r14
1498: 31 d2 xor edx,edx
149a: ff 90 d8 05 00 00 call QWORD PTR [rax+0x5d8]
```
Có thể thấy các function pointer từ bảng ảo ```rax+0x480```, ```rax+0x488```, ```rax+0x558```, ```rax+0x5d8``` đây chính là các JNI call và nó load các string resource ở offset ```2000```, ```2003```, ```2004```
Vì vậy ta sẽ viết một đoán script brute force để tìm flag
```java
import xyz.scriptctf.Main;
import xyz.scriptctf.NativeLoader;
public class SolveForeignDesign {
static int decEven8(int exp, int i){
int y = (exp ^ 0x5A) - 3*i;
int ch = (i + 19) ^ y;
return ch & 0xFF;
}
static int decOdd8(int exp, int i){
int ch = ((exp - 1) ^ 19) - 2*(i % 7);
return ch & 0xFF;
}
static String solveWithOrder(int[] first, int[] second){
int n = first.length + second.length;
char[] out = new char[n];
for (int i = 0; i < n; i++) {
int exp = (i < first.length) ? first[i] : second[i - first.length];
int ch = ((i & 1)==0) ? decEven8(exp, i) : decOdd8(exp, i);
int j = (int)(((long)5*i + 3) % n);
out[j] = (char)(ch & 0xFF);
}
return new String(out);
}
public static void main(String[] args) throws Exception {
NativeLoader.loadLibrary();
Main.initialize();
int[] ll = Main.ll;
int[] lll = Main.lll;
int n = ll.length + lll.length;
System.out.println("n="+n+" (ll="+ll.length+", lll="+lll.length+")");
String s1 = solveWithOrder(ll, lll); // exp theo i: ll rồi lll
String s2 = solveWithOrder(lll, ll ); // exp theo i: lll rồi ll
System.out.println("Candidate (order=ll||lll):");
System.out.println(s1);
System.out.println();
System.out.println("Candidate (order=lll||ll):");
System.out.println(s2);
}
}
```

```
FLAG : scriptCTF{nO_MOr3_n471v3_tr4N5l471on}
```
### Plastic Shield 2

Trước hết ta định dạng file xem

Có thể thấy binary không bị strip symbol vậy nên thử tìm xem có aes nào không

Điều này cho biết binary được compile cùng ```source aes.c``` và có gọi hàm ```AES_init_ctx_iv``` và ```AES_CBC_decrypt_buffer```.
Ta sẽ dùng ```objdump``` để disassemble binary xem sao
```python
308: 40077a: e8 a0 03 00 00 call 400b1f <AES_init_ctx_iv>
314: 400794: e8 33 13 00 00 call 401acc <AES_CBC_decrypt_buffer>
597:0000000000400b1f <AES_init_ctx_iv>:
1829:0000000000401acc <AES_CBC_decrypt_buffer>:
```
Nó giúp ta tìm thấy địa chỉ các call qua đó có thể dùng ```AES CBC``` để tìm flag
Giờ ta sẽ dùng ```gdb``` để đặt breakpoint.
Đây là một đoạn ```gdb``` tự động dừng đúng chỗ và in ra những thứ ta cần (```KEY```, ```IV```, ```ciphertext```, ```plaintext```)
```python
cat > ps2.gdb <<'GDB'
set pagination off
set confirm off
break *0x400b1f
commands
silent
echo \n-- AES_init_ctx_iv --\nKEY:\n
x/32bx $rsi
echo IV:\n
x/16bx $rdx
continue
end
break *0x401acc
commands
silent
set $BUF = $rsi
set $LEN = $rdx
echo \n-- BEFORE decrypt (ciphertext) --\n
x/64bx $BUF
finish
echo \n-- AFTER decrypt (plaintext) --\n
x/$LENbx $BUF
x/s $BUF
continue
end
run
GDB
```
Output là
```python
-- AES_init_ctx_iv --
KEY:
0x7fffffffd870: 0xf3 0x30 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffd878: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffd880: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffd888: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
IV:
0x7fffffffd850: 0xf3 0x30 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffd858: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
-- BEFORE decrypt (ciphertext) --
0x418ac0: 0xe2 0xea 0x0d 0x31 0x8a 0xf8 0x00 0x79
0x418ac8: 0xfb 0x56 0xdb 0x56 0x74 0xca 0x8c 0x27
0x418ad0: 0x4c 0x5f 0xd0 0xe9 0x20 0x19 0xac 0xd0
0x418ad8: 0x1e 0x89 0x17 0x1b 0xb8 0x89 0xf6 0xb1
0x418ae0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x418ae8: 0x21 0x05 0x02 0x00 0x00 0x00 0x00 0x00
0x418af0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x418af8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x0000000000400799 in main ()
```
Từ ```-- AES_init_ctx_iv --``` có thể thấy đây là lúc chương trình khởi tạo context ```AES``` bằng ```KEY``` và ```IV```
Còn ```-- BEFORE decrypt (ciphertext) --``` chính là dữ liệu mã hoá mà chương trình sẽ decrypt ```in‑place```
```python
e2 ea 0d 31 8a f8 00 79 fb 56 db 56 74 ca 8c 27
4c 5f d0 e9 20 19 ac d0 1e 89 17 1b b8 89 f6 b1
```
Giờ ta sẽ viết một đoạn brute-force để giải mã ```ciphertext``` xem
```python
from Crypto.Cipher import AES
ct = bytes.fromhex("e2ea0d318af80079fb56db5674ca8c274c5fd0e92019acd01e89171bb889f6b1")
for hi in range(256):
for lo in range(256):
key = bytes([hi, lo] + [0]*14)
cipher = AES.new(key, AES.MODE_CBC, iv=key)
pt = cipher.decrypt(ct)
if b"scriptCTF{" in pt:
print(f"KEY={key.hex()} PLAINTEXT={pt}")
exit()
```

```
FLAG : scriptCTF{00p513_n07_4641n!}
```
### vm

Giải nén xong sẽ được 2 files ```check.bin``` và ```reverse_me``` sau đó định dạng 2 files xem

Đây là dấu hiệu của một custom VM: binary đọc ```bytecode``` từ ```stdin``` rồi chạy
Giờ ta sẽ phân tích ```main``` bằng r2
```python
;-- section..text:
; DATA XREF from entry0 @ 0x1468(r)
┌ 961: int main (int argc, char **argv, char **envp);
│ afv: vars(6:sp[0x30..0x1060])
│ 0x00001080 4157 push r15 ; [13] -r-x section size 1391 named .text
│ 0x00001082 4156 push r14
│ 0x00001084 4154 push r12
│ 0x00001086 55 push rbp
│ 0x00001087 53 push rbx
│ 0x00001088 31db xor ebx, ebx
│ 0x0000108a 4881ec4010.. sub rsp, sym.imp.__stack_chk_fail ; 0x1040
│ 0x00001091 64488b2c25.. mov rbp, qword fs:[0x28]
│ 0x0000109a 4889ac2438.. mov qword [var_1038h], rbp ; [0x1038:8]=0xffffffe0e9000000
│ 0x000010a2 488d6c2430 lea rbp, [var_30h]
│ ┌─< 0x000010a7 eb1f jmp 0x10c8
..
│ │ ; CODE XREF from main @ 0x10dd(x)
│ ┌──> 0x000010b0 4883c301 add rbx, 1
│ ╎│ 0x000010b4 884500 mov byte [rbp], al
│ ╎│ 0x000010b7 4883c501 add rbp, 1
│ ╎│ 0x000010bb 4881fb0010.. cmp rbx, fcn.00001000 ; 0x1000
│ ┌───< 0x000010c2 0f84e9020000 je 0x13b1
│ │╎│ ; CODE XREF from main @ 0x10a7(x)
│ │╎└─> 0x000010c8 488b3d712f.. mov rdi, qword [obj.stdin] ; [0x4040:8]=0 ; FILE *stream
│ │╎ 0x000010cf e89cffffff call sym.imp.getc ; int getc(FILE *stream)
│ │╎ 0x000010d4 8d5001 lea edx, [rax + 1]
│ │╎ 0x000010d7 81e2fffeffff and edx, 0xfffffeff ; 4294967039
│ │└──< 0x000010dd 75d1 jne 0x10b0
│ │ 0x000010df 660fefc0 pxor xmm0, xmm0
│ │ 0x000010e3 0f29442410 movaps xmmword [var_10h], xmm0
│ │ 0x000010e8 0f29442420 movaps xmmword [var_20h], xmm0
│ │ 0x000010ed 4885db test rbx, rbx
│ │ ┌─< 0x000010f0 0f8478010000 je 0x126e
│ │ │ 0x000010f6 bda0860100 mov ebp, 0x186a0
│ │ │ 0x000010fb 31c0 xor eax, eax
│ │ │ 0x000010fd 31d2 xor edx, edx
│ │ │ ; CODE XREF from main @ 0x121a(x)
│ │┌──> 0x000010ff 0fb6541430 movzx edx, byte [rsp + rdx + 0x30]
│ │╎│ 0x00001104 488d4801 lea rcx, [rax + 1]
│ │╎│ 0x00001108 48894c2408 mov qword [var_8h], rcx
│ │╎│ 0x0000110d 80fa40 cmp dl, 0x40 ; elf_phdr
│ ┌────< 0x00001110 0f843f020000 je 0x1355
│ ┌─────< 0x00001116 7760 ja 0x1178
│ │││╎│ 0x00001118 80fa20 cmp dl, 0x20 ; "@"
│ ┌──────< 0x0000111b 0f84f5010000 je 0x1316
│ ││││╎│ 0x00001121 80fa30 cmp dl, 0x30 ; '0'
│ ┌───────< 0x00001124 0f84ad010000 je 0x12d7
│ │││││╎│ 0x0000112a 80fa10 cmp dl, 0x10
│ ────────< 0x0000112d 0f85f2000000 jne 0x1225
│ │││││╎│ 0x00001133 4839cb cmp rbx, rcx
│ ────────< 0x00001136 0f8ee4000000 jle 0x1220
│ │││││╎│ 0x0000113c 4883c002 add rax, 2
│ │││││╎│ 0x00001140 488d542408 lea rdx, [var_8h] ; int64_t arg3
│ │││││╎│ 0x00001145 4889de mov rsi, rbx ; signed int arg2
│ │││││╎│ 0x00001148 4889442408 mov qword [var_8h], rax
│ │││││╎│ 0x0000114d 0fb6440c30 movzx eax, byte [rsp + rcx + 0x30]
│ │││││╎│ 0x00001152 488d7c2430 lea rdi, [var_30h] ; int64_t arg1
│ │││││╎│ 0x00001157 83e007 and eax, 7
│ │││││╎│ 0x0000115a 4189c6 mov r14d, eax
│ │││││╎│ 0x0000115d e81e040000 call fcn.00001580
│ │││││╎│ 0x00001162 89c2 mov edx, eax
│ │││││╎│ 0x00001164 4c89f0 mov rax, r14
│ │││││╎│ 0x00001167 83e007 and eax, 7
│ │││││╎│ 0x0000116a 89548410 mov dword [rsp + rax*4 + 0x10], edx
│ ────────< 0x0000116e e98d000000 jmp 0x1200
..
│ │││││╎│ ; CODE XREF from main @ 0x1116(x)
│ ││└─────> 0x00001178 80fa60 cmp dl, 0x60 ; '`'
│ ││┌─────< 0x0000117b 0f840b010000 je 0x128c
│ │││││╎│ 0x00001181 80fa70 cmp dl, 0x70 ; 'p'
│ ────────< 0x00001184 0f84e4000000 je 0x126e
│ │││││╎│ 0x0000118a 80fa50 cmp dl, 0x50 ; 'P'
│ ────────< 0x0000118d 0f8592000000 jne 0x1225
│ │││││╎│ 0x00001193 4839cb cmp rbx, rcx
│ ────────< 0x00001196 0f8e84000000 jle 0x1220
│ │││││╎│ 0x0000119c 488d5002 lea rdx, [rax + 2]
│ │││││╎│ 0x000011a0 4839d3 cmp rbx, rdx
│ ────────< 0x000011a3 7e7b jle 0x1220
│ │││││╎│ 0x000011a5 4883c003 add rax, 3
│ │││││╎│ 0x000011a9 0fb64c0c30 movzx ecx, byte [rsp + rcx + 0x30]
│ │││││╎│ 0x000011ae 4889de mov rsi, rbx ; signed int arg2
│ │││││╎│ 0x000011b1 488d7c2430 lea rdi, [var_30h] ; int64_t arg1
│ │││││╎│ 0x000011b6 4889442408 mov qword [var_8h], rax
│ │││││╎│ 0x000011bb 0fb6441430 movzx eax, byte [rsp + rdx + 0x30]
│ │││││╎│ 0x000011c0 488d542408 lea rdx, [var_8h] ; int64_t arg3
│ │││││╎│ 0x000011c5 83e107 and ecx, 7
│ │││││╎│ 0x000011c8 83e007 and eax, 7
│ │││││╎│ 0x000011cb 4189ce mov r14d, ecx
│ │││││╎│ 0x000011ce 4189c7 mov r15d, eax
│ │││││╎│ 0x000011d1 e8aa030000 call fcn.00001580
│ │││││╎│ 0x000011d6 4c89f2 mov rdx, r14
│ │││││╎│ 0x000011d9 89c1 mov ecx, eax
│ │││││╎│ 0x000011db 4c89f8 mov rax, r15
│ │││││╎│ 0x000011de 83e207 and edx, 7
│ │││││╎│ 0x000011e1 83e007 and eax, 7
│ │││││╎│ 0x000011e4 8b448410 mov eax, dword [rsp + rax*4 + 0x10]
│ │││││╎│ 0x000011e8 39449410 cmp dword [rsp + rdx*4 + 0x10], eax
│ ────────< 0x000011ec 7412 je 0x1200
│ │││││╎│ 0x000011ee 89c8 mov eax, ecx
│ │││││╎│ 0x000011f0 4889442408 mov qword [var_8h], rax
│ │││││╎│ 0x000011f5 4839c3 cmp rbx, rax
│ ────────< 0x000011f8 0f8c28020000 jl 0x1426
│ │││││╎│ 0x000011fe 6690 nop
│ │││││╎│ ; XREFS: CODE 0x0000116e CODE 0x000011ec CODE 0x000012d2 CODE 0x00001311 CODE 0x00001350
│ │││││╎│ ; XREFS: CODE 0x00001371
│ ────────> 0x00001200 488b442408 mov rax, qword [var_8h]
│ │││││╎│ 0x00001205 4889c2 mov rdx, rax
│ │││││╎│ 0x00001208 4839d8 cmp rax, rbx
│ ────────< 0x0000120b 7361 jae 0x126e
│ │││││╎│ 0x0000120d 4883ed01 sub rbp, 1
│ ────────< 0x00001211 0f84bc010000 je 0x13d3
│ │││││╎│ 0x00001217 4839c3 cmp rbx, rax
│ │││││└──< 0x0000121a 0f8fdffeffff jg 0x10ff
│ │││││ │ ; XREFS: CODE 0x00001136 CODE 0x00001196 CODE 0x000011a3 CODE 0x0000128f CODE 0x000012da
│ │││││ │ ; XREFS: CODE 0x000012e7 CODE 0x00001319 CODE 0x00001326
│ ─────┌──> 0x00001220 e82b030000 call fcn.00001550
│ │││││╎│ ; CODE XREFS from main @ 0x112d(x), 0x118d(x)
│ ────────> 0x00001225 488b0d342e.. mov rcx, qword [obj.stderr] ; [0x4060:8]=0 ; FILE *stream
│ │││││╎│ 0x0000122c ba1c000000 mov edx, 0x1c ; size_t nitems
│ │││││╎│ 0x00001231 be01000000 mov esi, 1 ; size_t size
│ │││││╎│ 0x00001236 488d3d0c0e.. lea rdi, str.wtf_is_this_op_bruh_:skull:_n ; 0x2049 ; "wtf is this op bruh :skull:\n" ; const void *ptr
│ │││││╎│ 0x0000123d e81efeffff call sym.imp.fwrite ; size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
│ │││││╎│ ; CODE XREF from main @ 0x13ce(x)
│ ────────> 0x00001242 b801000000 mov eax, 1
│ │││││╎│ ; CODE XREFS from main @ 0x128a(x), 0x13ac(x)
│ ────────> 0x00001247 488b942438.. mov rdx, qword [var_1038h]
│ │││││╎│ 0x0000124f 64482b1425.. sub rdx, qword fs:[0x28]
│ ────────< 0x00001258 0f85c3010000 jne 0x1421
│ │││││╎│ 0x0000125e 4881c44010.. add rsp, sym.imp.__stack_chk_fail ; 0x1040
│ │││││╎│ 0x00001265 5b pop rbx
│ │││││╎│ 0x00001266 5d pop rbp
│ │││││╎│ 0x00001267 415c pop r12
│ │││││╎│ 0x00001269 415e pop r14
│ │││││╎│ 0x0000126b 415f pop r15
│ │││││╎│ 0x0000126d c3 ret
│ │││││╎│ ; CODE XREFS from main @ 0x10f0(x), 0x1184(x), 0x120b(x)
│ ──────└─> 0x0000126e 817c241469.. cmp dword [var_14h], 0x69696969 ; 'iiii'
│ │││││╎┌─< 0x00001276 0f8422010000 je 0x139e
│ │││││╎│ 0x0000127c 488d3de30d.. lea rdi, str.lol__nice_try ; 0x2066 ; "lol, nice try" ; const char *s
│ │││││╎│ 0x00001283 e8a8fdffff call sym.imp.puts ; int puts(const char *s)
│ │││││╎│ 0x00001288 31c0 xor eax, eax
│ ────────< 0x0000128a ebbb jmp 0x1247
│ │││││╎│ ; CODE XREF from main @ 0x117b(x)
│ ││└─────> 0x0000128c 4839cb cmp rbx, rcx
│ ────────< 0x0000128f 7e8f jle 0x1220
│ ││ ││╎│ 0x00001291 4883c002 add rax, 2
│ ││ ││╎│ 0x00001295 488d542408 lea rdx, [var_8h] ; int64_t arg3
│ ││ ││╎│ 0x0000129a 4889de mov rsi, rbx ; signed int arg2
│ ││ ││╎│ 0x0000129d 440fb67c0c30 movzx r15d, byte [rsp + rcx + 0x30]
│ ││ ││╎│ 0x000012a3 488d7c2430 lea rdi, [var_30h] ; int64_t arg1
│ ││ ││╎│ 0x000012a8 4889442408 mov qword [var_8h], rax
│ ││ ││╎│ 0x000012ad e8ce020000 call fcn.00001580
│ ││ ││╎│ 0x000012b2 83f816 cmp eax, 0x16
│ ││┌─────< 0x000012b5 0f873f010000 ja 0x13fa
│ │││││╎│ 0x000012bb 89c0 mov eax, eax
│ │││││╎│ 0x000012bd 488d0dfc0d.. lea rcx, str._______________________ ; 0x20c0 ; "_______________________"
│ │││││╎│ 0x000012c4 4c89fa mov rdx, r15
│ │││││╎│ 0x000012c7 0fb60401 movzx eax, byte [rcx + rax]
│ │││││╎│ 0x000012cb 83e207 and edx, 7
│ │││││╎│ 0x000012ce 89449410 mov dword [rsp + rdx*4 + 0x10], eax
│ ────────< 0x000012d2 e929ffffff jmp 0x1200
│ │││││╎│ ; CODE XREF from main @ 0x1124(x)
│ └───────> 0x000012d7 4839cb cmp rbx, rcx
│ ────────< 0x000012da 0f8e40ffffff jle 0x1220
│ ││││╎│ 0x000012e0 488d7002 lea rsi, [rax + 2]
│ ││││╎│ 0x000012e4 4839f3 cmp rbx, rsi
│ ────────< 0x000012e7 0f8e33ffffff jle 0x1220
│ ││││╎│ 0x000012ed 0fb6540c30 movzx edx, byte [rsp + rcx + 0x30]
│ ││││╎│ 0x000012f2 4883c003 add rax, 3
│ ││││╎│ 0x000012f6 4889442408 mov qword [var_8h], rax
│ ││││╎│ 0x000012fb 4889d0 mov rax, rdx
│ ││││╎│ 0x000012fe 0fb6543430 movzx edx, byte [rsp + rsi + 0x30]
│ ││││╎│ 0x00001303 83e007 and eax, 7
│ ││││╎│ 0x00001306 83e207 and edx, 7
│ ││││╎│ 0x00001309 8b549410 mov edx, dword [rsp + rdx*4 + 0x10]
│ ││││╎│ 0x0000130d 31548410 xor dword [rsp + rax*4 + 0x10], edx
│ ────────< 0x00001311 e9eafeffff jmp 0x1200
│ ││││╎│ ; CODE XREF from main @ 0x111b(x)
│ └──────> 0x00001316 4839cb cmp rbx, rcx
│ ────────< 0x00001319 0f8e01ffffff jle 0x1220
│ │││╎│ 0x0000131f 488d7002 lea rsi, [rax + 2]
│ │││╎│ 0x00001323 4839f3 cmp rbx, rsi
│ │││└──< 0x00001326 0f8ef4feffff jle 0x1220
│ │││ │ 0x0000132c 0fb6540c30 movzx edx, byte [rsp + rcx + 0x30]
│ │││ │ 0x00001331 4883c003 add rax, 3
│ │││ │ 0x00001335 4889442408 mov qword [var_8h], rax
│ │││ │ 0x0000133a 4889d0 mov rax, rdx
│ │││ │ 0x0000133d 0fb6543430 movzx edx, byte [rsp + rsi + 0x30]
│ │││ │ 0x00001342 83e007 and eax, 7
│ │││ │ 0x00001345 83e207 and edx, 7
│ │││ │ 0x00001348 8b549410 mov edx, dword [rsp + rdx*4 + 0x10]
│ │││ │ 0x0000134c 01548410 add dword [rsp + rax*4 + 0x10], edx
│ ────────< 0x00001350 e9abfeffff jmp 0x1200
│ │││ │ ; CODE XREF from main @ 0x1110(x)
│ │└────> 0x00001355 488d542408 lea rdx, [var_8h] ; int64_t arg3
│ │ │ │ 0x0000135a 4889de mov rsi, rbx ; signed int arg2
│ │ │ │ 0x0000135d 488d7c2430 lea rdi, [var_30h] ; int64_t arg1
│ │ │ │ 0x00001362 e819020000 call fcn.00001580
│ │ │ │ 0x00001367 89c0 mov eax, eax
│ │ │ │ 0x00001369 4889442408 mov qword [var_8h], rax
│ │ │ │ 0x0000136e 4839c3 cmp rbx, rax
│ ────────< 0x00001371 0f8d89feffff jge 0x1200
│ │ │ │ 0x00001377 488b0de22c.. mov rcx, qword [obj.stderr] ; [0x4060:8]=0 ; FILE *stream
│ │ │ │ 0x0000137e ba22000000 mov edx, 0x22 ; '\"' ; size_t nitems
│ │ │ │ 0x00001383 be01000000 mov esi, 1 ; size_t size
│ │ │ │ 0x00001388 488d3de90c.. lea rdi, str.y_u_trying_to_jmp_to_those_places_n ; 0x2078 ; "y u trying to jmp to those places\n" ; const void *ptr
│ │ │ │ 0x0000138f e8ccfcffff call sym.imp.fwrite ; size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
│ │ │ │ 0x00001394 bf01000000 mov edi, 1 ; int status
│ │ │ │ 0x00001399 e8b2fcffff call sym.imp.exit ; void exit(int status)
│ │ │ │ ; CODE XREF from main @ 0x1276(x)
│ │ │ └─> 0x0000139e 488d3d1b0d.. lea rdi, str._______________________ ; 0x20c0 ; "_______________________" ; const char *s
│ │ │ 0x000013a5 e886fcffff call sym.imp.puts ; int puts(const char *s)
│ │ │ 0x000013aa 31c0 xor eax, eax
│ ────────< 0x000013ac e996feffff jmp 0x1247
│ │ │ ; CODE XREF from main @ 0x10c2(x)
│ │ └───> 0x000013b1 488b0da82c.. mov rcx, qword [obj.stderr] ; [0x4060:8]=0 ; FILE *stream
│ │ 0x000013b8 ba17000000 mov edx, 0x17 ; size_t nitems
│ │ 0x000013bd be01000000 mov esi, 1 ; size_t size
│ │ 0x000013c2 488d3d400c.. lea rdi, str.y_ur_code_so_long_bruh_n ; 0x2009 ; "y ur code so long bruh\n" ; const void *ptr
│ │ 0x000013c9 e892fcffff call sym.imp.fwrite ; size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
│ ────────< 0x000013ce e96ffeffff jmp 0x1242
│ │ ; CODE XREF from main @ 0x1211(x)
│ ────────> 0x000013d3 488b0d862c.. mov rcx, qword [obj.stderr] ; [0x4060:8]=0 ; FILE *stream
│ │ 0x000013da ba13000000 mov edx, 0x13 ; size_t nitems
│ │ 0x000013df be01000000 mov esi, 1 ; size_t size
│ │ 0x000013e4 488d3d360c.. lea rdi, str.y_u_take_this_long_n ; 0x2021 ; "y u take this long\n" ; const void *ptr
│ │ 0x000013eb e870fcffff call sym.imp.fwrite ; size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
│ │ 0x000013f0 bf01000000 mov edi, 1 ; int status
│ │ 0x000013f5 e856fcffff call sym.imp.exit ; void exit(int status)
│ │ ; CODE XREF from main @ 0x12b5(x)
│ └─────> 0x000013fa 488b0d5f2c.. mov rcx, qword [obj.stderr] ; [0x4060:8]=0 ; FILE *stream
│ 0x00001401 ba13000000 mov edx, 0x13 ; size_t nitems
│ 0x00001406 be01000000 mov esi, 1 ; size_t size
│ 0x0000140b 488d3d230c.. lea rdi, str.i_told_u_no_pwning_n ; 0x2035 ; "i told u no pwning\n" ; const void *ptr
│ 0x00001412 e849fcffff call sym.imp.fwrite ; size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
│ 0x00001417 bf01000000 mov edi, 1 ; int status
│ 0x0000141c e82ffcffff call sym.imp.exit ; void exit(int status)
│ ; CODE XREF from main @ 0x1258(x)
│ ────────> 0x00001421 e81afcffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ ; CODE XREF from main @ 0x11f8(x)
│ ────────> 0x00001426 488b0d332c.. mov rcx, qword [obj.stderr] ; [0x4060:8]=0 ; FILE *stream
│ 0x0000142d ba1f000000 mov edx, 0x1f ; size_t nitems
│ 0x00001432 be01000000 mov esi, 1 ; size_t size
│ 0x00001437 488d3d620c.. lea rdi, str.y_r_u_trying_to_pwn_this_vm_:__n ; 0x20a0 ; "y r u trying to pwn this vm :(\n" ; const void *ptr
│ 0x0000143e e81dfcffff call sym.imp.fwrite ; size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
│ 0x00001443 bf01000000 mov edi, 1 ; int status
└ 0x00001448 e803fcffff call sym.imp.exit ; void exit(int status)
```
Từ đoạn
```python
cmp dl, 0x10 -> je 0x1133 ; MOVI
cmp dl, 0x20 -> je 0x1316 ; ADD
cmp dl, 0x30 -> je 0x12d7 ; XOR
cmp dl, 0x40 -> je 0x1355 ; JMP (điều hướng PC)
cmp dl, 0x50 -> ... 0x1193 ; JNE (so sánh/nhảy)
cmp dl, 0x60 -> je 0x128c ; MOVS (lấy byte từ S[idx])
```
Có thể tháy ```VM``` có ```opcode handler``` đặt tại các địa chỉ trên vậy ta sẽ dump từng ```handler``` để xem ( do độ dài bài viết có giới hạn nên không thể up cụ thể lên được)
```python
s 0x12d7
pd 40
s 0x1316
pd 40
s 0x1133
pd 40
s 0x128c
pd 40
s 0x1193
pd 60
```
Giờ sẽ phân tích ```check.bin```
```python
00000000: 60 00 01 00 00 00 60 01 03 00 00 00 30 00 01 60 `.....`.....0..`
00000010: 02 15 00 00 00 20 00 02 10 03 79 00 00 00 50 00 ..... ....y...P.
00000020: 03 28 0a 00 00 60 00 02 00 00 00 60 01 0b 00 00 .(...`.....`....
00000030: 00 20 00 01 60 02 13 00 00 00 30 00 02 10 03 90 . ..`.....0.....
00000040: 00 00 00 50 00 03 28 0a 00 00 60 00 0d 00 00 00 ...P..(...`.....
00000050: 60 01 0e 00 00 00 20 00 01 60 02 11 00 00 00 20 `..... ..`.....
00000060: 00 02 10 03 df 00 00 00 50 00 03 28 0a 00 00 60 ........P..(...`
00000070: 00 0e 00 00 00 60 01 10 00 00 00 20 00 01 60 02 .....`..... ..`.
00000080: 12 00 00 00 30 00 02 10 03 f9 00 00 00 50 00 03 ....0........P..
00000090: 28 0a 00 00 60 00 04 00 00 00 60 01 0e 00 00 00 (...`.....`.....
```
Dựa vào đây kết hợp với ```reverse_me``` ta sẽ viết một đoạn script dùng ```Z3``` để giải mã nó
```python
# solve_vm_symbolic_clean.py
# Emulate VM đúng layout (theo dump r2 của bạn) và solve S[0..22] bằng Z3
# KHÔNG ép prefix/charset để tránh Unsat do giả định sai.
from z3 import *
BIN = "check.bin"
MASK32 = BitVecVal(0xffffffff, 32)
def u32(b: bytes) -> int:
return int.from_bytes(b, "little")
def zx8_to32(x: BitVecRef) -> BitVecRef:
return ZeroExt(24, Extract(7, 0, x))
def main():
data = open(BIN, "rb").read()
n = len(data)
pc = 0
# 23 byte chưa biết (0..22)
S = [BitVec(f"S{i}", 32) for i in range(23)]
s = Solver()
for i in range(23):
# Cho phép ASCII in được để tránh rác; bỏ hẳn prefix/charset để không mâu thuẫn
s.add(UGE(S[i], 32), ULE(S[i], 126))
# 8 thanh ghi 32-bit
R = [BitVecVal(0, 32) for _ in range(8)]
steps = 0
MAX_STEPS = 2_000_000
while 0 <= pc < n and steps < MAX_STEPS:
steps += 1
op = data[pc]; pc += 1
if op == 0x10:
# 0x10 r imm32
if pc + 5 > n: break
r = data[pc] & 7
imm = u32(data[pc+1:pc+5]); pc += 5
R[r] = BitVecVal(imm, 32)
elif op == 0x60:
# 0x60 r idx32
if pc + 5 > n: break
r = data[pc] & 7
idx = u32(data[pc+1:pc+5]); pc += 5
# chỉ số phải trong 0..22; đây là hằng số từ bytecode
if not (0 <= idx <= 0x16):
# bytecode xấu: bỏ qua để tránh crash
break
R[r] = zx8_to32(S[idx])
elif op in (0x20, 0x30):
# 0x20/0x30 a b
if pc + 2 > n: break
a = data[pc] & 7; b = data[pc+1] & 7; pc += 2
if op == 0x20:
R[a] = (R[a] + R[b]) & MASK32
else:
R[a] = R[a] ^ R[b]
elif op == 0x50:
# 0x50 a b imm32 (JNE nếu KHÔNG bằng thì nhảy)
if pc + 6 > n: break
a = data[pc] & 7; b = data[pc+1] & 7
tgt = u32(data[pc+2:pc+6]); pc += 6
# theo nhánh ĐÚNG (không nhảy): R[a] == R[b]
s.add(R[a] == R[b])
elif op == 0x40:
# 0x40 imm32 (JMP vô điều kiện, không có a,b)
if pc + 4 > n: break
tgt = u32(data[pc:pc+4]); pc = tgt
elif op == 0x70:
# 0x70 (no params)
pass
elif op == 0x90:
# 0x90 (no params)
pass
else:
# opcode lạ → dừng để tránh lệch PC
break
assert s.check() == sat, "Unsat — nếu vẫn lỗi, gửi thêm dump op lạ để chỉnh."
m = s.model()
out = ''.join(chr((m[S[i]].as_long() & 0xff)) for i in range(23))
print("[+] S[0..22] ->", out)
# Nếu đúng format flag thì in luôn
if out.startswith("scriptCTF{") and out.endswith("}"):
print("[+] FLAG:", out)
else:
print("[?] Not in canonical form; nếu cần mình sẽ map tiếp tùy bytecode.")
# Bạn có thể vẫn đọc ra được dạng khác rồi ráp lại.
if __name__ == "__main__":
main()
```

```
FLAG : scriptCTF{5up3r_dup3r_345y_vm_r3v}
```