---
# System prepended metadata

title: scriptCTF 2025 Writeup

---

# scriptCTF 2025 Writeup
>By: TAS.ElPulga
## Crypto
### Secure-Server
@ansobad x @daq 
![image](https://hackmd.io/_uploads/H1h655xKxe.png)

Đượ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
![image](https://hackmd.io/_uploads/S1OlDaeYex.png)

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
![image](https://hackmd.io/_uploads/BJ9Gd6xtxx.png)
```
FLAG: scriptCTF{x0r_1s_not_s3cur3!!!!}
```

### RSA-1
@ansobad 
![image](https://hackmd.io/_uploads/HJcFVnlYll.png)

#### Đề 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
![image](https://hackmd.io/_uploads/SJIoK2ltex.png)
#### Đề 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 
![image](https://hackmd.io/_uploads/BySxy6eYeg.png)
#### Đề 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 
![image](https://hackmd.io/_uploads/ryogN6eFxl.png)
#### Đề 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
![image](https://hackmd.io/_uploads/Hy8L78lFex.png)
Khi binwalk file ta được một file tên là ```flag.txt```
![image](https://hackmd.io/_uploads/HknrN8xKgg.png)
Sau đó thử xem trong đó có gì
![image](https://hackmd.io/_uploads/S10tEIxFee.png)
```
FLAG: scriptCTF{1_l0v3_m461c_7r1ck5}
```
### pdf
![image](https://hackmd.io/_uploads/rJAkrIxYlx.png)
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
![image](https://hackmd.io/_uploads/Bkmc8LlKxg.png)
Sau khi binwalk ra được
![image](https://hackmd.io/_uploads/H18GvUeYel.png)
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õ
![image](https://hackmd.io/_uploads/BJKH68gYxx.png)
Nó là d41v3ron hoặc d41v3ran
Ta sẽ sử dụng cái này để làm password cho ```justsomezip.zip```
![image](https://hackmd.io/_uploads/HJGFpUgtlx.png)
```
FLAG : scriptCTF{1_l0ve_d41_v3r0n}
```
## Web 
### Renderer
@daq 
![image](https://hackmd.io/_uploads/rkJM5Xxtxe.png)
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
![image](https://hackmd.io/_uploads/ryC70meYgl.png)

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`
![image](https://hackmd.io/_uploads/SkZcx4lYxe.png)

Tiếp theo chỉ cần gửi request đến `/developer` với secret cookie đó thì sẽ nhận được flag
![image](https://hackmd.io/_uploads/S1EneNgtxg.png)

```
FLAG: scriptCTF{my_c00k135_4r3_n0t_s4f3!_3da49da56983}
```

## OSINT 
### The Insider 
@daq 

![image](https://hackmd.io/_uploads/Sk5Rh-xtgx.png)

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
![image](https://hackmd.io/_uploads/SyVG6bxtgg.png)

Thấy có NoobMaster, vào profile của người này trên discord thì sẽ thấy được flag
![image](https://hackmd.io/_uploads/SykDp-ltgl.png)

```
FLAG: scriptCTF{1ts_0bv10u5ly_j0hn_d03_aka_n00bm4573r}
```

### The Insider 2
@daq  
![image](https://hackmd.io/_uploads/ryE9Tbltxx.png)

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
![image](https://hackmd.io/_uploads/SyS6a-xKxl.png)

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
![image](https://hackmd.io/_uploads/SkvW0bgFlx.png)

Mở ra thì thấy thông tin đăng nhập với username và password đều là `scriptCTF2026`
![image](https://hackmd.io/_uploads/Hye-NAZxKgg.png)

Quay lại với trang web còn lại ở bio của họ, truy cập trang web đó
![image](https://hackmd.io/_uploads/HJbUAWlKgx.png)

Ấn vào link dẫn tiếp đến một trang web login
![image](https://hackmd.io/_uploads/BkMO0WeFex.png)

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

Sau đó nhận được flag
![image](https://hackmd.io/_uploads/SkY9CWxKee.png)

```
FLAG: scriptCTF{scriptCTF_2026_leaked?!!}
```

### The Insider 3
@daq 
![image](https://hackmd.io/_uploads/rJ5gHXlFgl.png)
Tiếp tục ở trang github đó, thấy có repo `scriptCTF26`, thử vào đó
![image](https://hackmd.io/_uploads/H1pGrQltel.png)

Thấy folder `OSINT` và folder `Leaked`, vào đó và thấy được flag
![image](https://hackmd.io/_uploads/SJZHr7lFex.png)

```
FLAG: scriptCTF{2026_fl4g_f0und_1n_2025}
```

### The Insider 4
@daq
![image](https://hackmd.io/_uploads/rJBuHQeYge.png)

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`
![image](https://hackmd.io/_uploads/B1hPSXxKlg.png)
![image](https://hackmd.io/_uploads/r1FkU7eKxg.png)
![image](https://hackmd.io/_uploads/rknx8Qltge.png)

Ngoài ra còn có file `.secret`, thử đọc nó và thấy được như sau
![image](https://hackmd.io/_uploads/ByPrUXgtgx.png)
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
![image](https://hackmd.io/_uploads/ry4gDmxFlx.png)

Khi tra cả Wendell family và fireworks trên google thì ra được địa điểm là ở Rockport, Texas, Hoa Kì.
![image](https://hackmd.io/_uploads/H1jSDQlFll.png)

Khi nhìn lại trên ảnh `room.jpg`, thấy có 2 tòa nhà khá đặc trưng ở xa xa
![image](https://hackmd.io/_uploads/S1GFPQlYgl.png)

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
![image](https://hackmd.io/_uploads/SyiVOmxKxx.png)
![image](https://hackmd.io/_uploads/S1uSu7gYxx.png)

Đị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
![image](https://hackmd.io/_uploads/rJ8gYmlYxl.png)

```
FLAG: scriptCTF{901_Hwy_35_N_111}
```
## Misc
### Div
@daq 
![image](https://hackmd.io/_uploads/BkxNVhVlYge.png)
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
![image](https://hackmd.io/_uploads/HJ06xHxKgl.png)

```
FLAG: scriptCTF{70_1nf1n17y_4nd_b3y0nd_71a333f28038}
```
### emoji
@daq 
![image](https://hackmd.io/_uploads/B1fWzHltxg.png)
Có file `out.txt`
![image](https://hackmd.io/_uploads/Skh6SBetee.png)

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))
```
![image](https://hackmd.io/_uploads/rJrT8BeKge.png)

```
FLAG: scriptCTF{3m0j1_3nc0d1ng_1s_w31rd_4nd_fun!1e46d}
```

### Enchant
@daq 
![image](https://hackmd.io/_uploads/HJqKwBxKxg.png)
Có file `enc.txt`
![image](https://hackmd.io/_uploads/BkfyuSgtgg.png)
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
![image](https://hackmd.io/_uploads/H1hU_HgYgg.png)

```
FLAG: scriptCTF{Minecraftisfun}
```

### Div 2
@daq 
![image](https://hackmd.io/_uploads/B1BNnaeFee.png)
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
![image](https://hackmd.io/_uploads/BJl3aTgKle.png)

```
FLAG: scriptCTF{b1n4ry_s34rch_u51ng_d1v1s10n?!!_80cbdb6d1420}
```

### Subtract
@hphuc204 
![image](https://hackmd.io/_uploads/rkl9PCLgKlg.png)
Sau khi giải nén ta được 1 file duy nhất là ```coordinates.txt```
![image](https://hackmd.io/_uploads/S1KbkPlKee.png)
Xem thử vài dòng đầu có gì
![image](https://hackmd.io/_uploads/HyzzgPlKgg.png)
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()
```                     
![image](https://hackmd.io/_uploads/HJ-wMveYex.png)
Sau khi đổi sang leetcode ta sẽ thấy được flag
```
FLAG : scriptCTF{5ub7r4c7_7h3_n01s3}
```
## Programming
### Sums
@daq 
![image](https://hackmd.io/_uploads/SJ-PzCeKgx.png)
Đượ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
![image](https://hackmd.io/_uploads/SJfIr0xYeg.png)
```
FLAG: scriptCTF{1_w4n7_m0r3_5um5_6c4e6a9f1459}
```

### More Divisors
@daq 
![image](https://hackmd.io/_uploads/r1sASCetxe.png)
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ố
![image](https://hackmd.io/_uploads/ByvGURxKxx.png)

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
![image](https://hackmd.io/_uploads/HJ_VPAgtle.png)
```
FLAG: scriptCTF{7H3_m0r3_f4C70r5_7h3_b3773r_4219cc5743f6}
```
### Windows To Infinity
@hphuc204 
![image](https://hackmd.io/_uploads/SyYN7DgYlx.png)
Đề 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ố 
![image](https://hackmd.io/_uploads/S1E3XPlYge.png)
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;
}
```                 
![image](https://hackmd.io/_uploads/BkcqswgFlx.png)
```
FLAG : scriptCTF{i_10v3_m4_w1nd0wwwwwwww5_dd197441ce24}
```

### Back From Where
@daq 
![image](https://hackmd.io/_uploads/BJaCwAxtle.png)
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
![image](https://hackmd.io/_uploads/Syj9d0xFee.png)

```
FLAG: scriptCTF{d0nt_u_ju5t_!@ve_z%ro3s???_ecb390924303}
```
## Rev
@hphuc204  
### Plastic Shield
![image](https://hackmd.io/_uploads/Sk2PtOxKee.png)
Trước hết ta sẽ định dạng file và xem những header đầu
![image](https://hackmd.io/_uploads/BJ-TATxYee.png)
Có thể thấy không có overlay ở cuối
Ta sẽ liệt kê các hàm import từ libc xem
![image](https://hackmd.io/_uploads/BJShJReYel.png)
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ì
![image](https://hackmd.io/_uploads/HJpTgClKgx.png)
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.")
```
![image](https://hackmd.io/_uploads/SJm9ERetxg.png)
```
FLAG: scriptCTF{20_cau541i71e5_d3f3n5es_d0wn}
```
### ForeignDesign
![image](https://hackmd.io/_uploads/rJ015ReFgl.png)
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
![image](https://hackmd.io/_uploads/S1sDqCeFee.png)
Thấy có 3 mục ```linux64/libforeign.so```, ```linux32/libforeign.so```, ```win32/foreign.dll``` khá lạ
Định dạng file linux/libforeign.so
![image](https://hackmd.io/_uploads/BkDELpWKgl.png)
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```
![image](https://hackmd.io/_uploads/B15qLpZFll.png)
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);
  }
}
```
![image](https://hackmd.io/_uploads/SJV4TTbYex.png)
```
FLAG : scriptCTF{nO_MOr3_n471v3_tr4N5l471on}
```
### Plastic Shield 2
![image](https://hackmd.io/_uploads/r1rtTpWKgg.png)
Trước hết ta định dạng file xem
![image](https://hackmd.io/_uploads/SJSu06bKge.png)
Có thể thấy binary không bị strip symbol vậy nên thử tìm xem có aes nào không
![image](https://hackmd.io/_uploads/SyfwJA-txe.png)
Đ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()
```
![image](https://hackmd.io/_uploads/ry7SzEfFll.png)
```
FLAG : scriptCTF{00p513_n07_4641n!}
```
### vm
![image](https://hackmd.io/_uploads/Bk5ofVftgg.png)
Giải nén xong sẽ được 2 files ```check.bin``` và ```reverse_me``` sau đó định dạng 2 files xem
![image](https://hackmd.io/_uploads/Bk6nVlQtge.png)
Đâ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()
```
![image](https://hackmd.io/_uploads/BkzQRlQFgl.png)
```
FLAG : scriptCTF{5up3r_dup3r_345y_vm_r3v}
```