---
# System prepended metadata

title: 'TJCTF 2025 [FOR]+[MISC]'

---

# TJCTF 2025 [FOR+MISC]
![image](https://hackmd.io/_uploads/Sy2ehUm7xg.png)

## 1. forensics/hidden-message
![image](https://hackmd.io/_uploads/Sk2ypL7Qex.png)
![suspicious](https://hackmd.io/_uploads/H1UChIXmee.png)
![image](https://hackmd.io/_uploads/SJs-aI77lx.png)
> Flag: tjctf{steganography_is_fun}

---
## 2. forensics/deep-layers
![image](https://hackmd.io/_uploads/BkSNaLXQee.png)
![image](https://hackmd.io/_uploads/SysLTLmmel.png)
![image](https://hackmd.io/_uploads/rkJu6LmXeg.png)
Binwalk ra:  
![image](https://hackmd.io/_uploads/Sk9KTLm7le.png)
Giải nén với pass đã cho:  
![image](https://hackmd.io/_uploads/BynipUQmxe.png)
> Flag: tjctf{p0lygl0t_r3bb1t_h0l3}

---
## 3. forensics/footprint
![image](https://hackmd.io/_uploads/BJxRaL7Qlg.png)
> https://github.com/gehaxelt/Python-dsstore
![image](https://hackmd.io/_uploads/SJmUyv77lg.png)
Decode từng dòng thì có được 2 part flag ở:  
```
aXNfdXNlZnVsP30gICAgIA -> is_useful?}     
b-PeUWwmlHzmh613ikEFWw -> None
b0HMxEHbs7pA9uHtxRPPTQ -> None
Bg9XKyNnYRSpUIYeK_2knA -> None
CE4CzpPMjNHuYeLi2dLNHg -> None
CPyvVdX_ynvUTdxXWYr7Mw -> None
D0FvxLGtoba2wKJQQEjUKA -> None
D3LWCMzIQyc12SQUw5uDnw -> None
DFhc0ROB762T-pZvtdPFqA -> None
dGpjdGZ7ZHNfc3RvcmVfIA -> tJctF{ds_store
```
> Flag: tjctf{ds_store_is_useful?}

---
## 4. forensics/album-cover
![image](https://hackmd.io/_uploads/Bk32zDQQle.png)
```python=
import wave
from PIL import Image
import numpy as np
#sample_rate = 44100
with wave.open('flag.wav', 'rb') as w:
    frames = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16)
    print(w.getnframes())
    sampwidth = w.getsampwidth() # 2
    nchannels = w.getnchannels() # 1
    w.close()
arr = np.array(frames)
img = arr.reshape((441, 444))
img = (img + 32767) / 65535 * 255
img = img.astype(np.uint8)
img = Image.fromarray(img)
img = img.convert('L')
img.save('albumcover.png')
```
![albumcover](https://hackmd.io/_uploads/SkGz9WNXle.png)
Solve script:  
```python=
import numpy as np
from PIL import Image
import wave

# Đọc ảnh đầu vào
img = Image.open("albumcover.png").convert("L")
arr = np.array(img)

# Khôi phục về khoảng giá trị int16
arr = (arr.astype(np.float32) / 255) * 65535 - 32767
arr = arr.astype(np.int16)

# Ghi ra file WAV
with wave.open("recovered.wav", "wb") as w:
    w.setnchannels(1)
    w.setsampwidth(2)  # 16-bit = 2 bytes
    w.setframerate(44100)
    w.writeframes(arr.tobytes())
```
![image](https://hackmd.io/_uploads/r19Y9W4Qel.png)
> Flag: tjctf{THIS-EASTER-EGG-IS-PRETTY-COOL}

---
## 5. forensics/packet-palette
![image](https://hackmd.io/_uploads/rJ_esWEXlx.png)
```python=
#!/usr/bin/env python3
from scapy.all import rdpcap, IP, TCP

# 1. Đọc tất cả packet từ file pcap
pcap_file = "chall.pcapng"
pkts = rdpcap(pcap_file)

# 2. Khởi tạo buffer để chứa data đã ghép
data_all = b""

# 3. Duyệt qua từng packet
for pkt in pkts:
    # Kiểm tra có IP/TCP hay không
    if IP in pkt and TCP in pkt:
        # Lọc IP nguồn và độ dài payload > 300
        if pkt[IP].src == "10.10.10.1":
            payload = bytes(pkt[TCP].payload)
            if len(payload) > 300:
                # Bỏ 12 byte đầu và ghép phần còn lại
                data_all += payload[12:]

# 4. Ghi data đã ghép ra file
with open("output.png", "wb") as f:
    f.write(data_all)

print(f"Đã lấy và ghép xong {len(data_all)} bytes, lưu vào output.bin")
```
![output](https://hackmd.io/_uploads/BkfDAibNQel.png)
> Flag: tjctf{usb1p_f13g_1ns1d3_3_pr0t0c0l}

---
## 6. forensics/quant
![image](https://hackmd.io/_uploads/SyUm3WN7xe.png)
![lost](https://hackmd.io/_uploads/rksG3-VQll.jpg)
```python=
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
replace_quant.py

Đọc file JPEG gốc (ví dụ "lost.jpg"), tìm mọi segment DQT (0xFFDB),
thay toàn bộ giá trị trong mỗi quantization table thành 0x01,
rồi ghi kết quả ra file mới (ví dụ "lost_modified.jpg").
"""

import sys

def fix_quant_tables(input_path: str, output_path: str):
    # Đọc toàn bộ nhị phân của JPEG vào memory
    with open(input_path, 'rb') as f:
        data = bytearray(f.read())

    i = 0
    n = len(data)
    count = 0

    # Duyệt byte-by-byte để tìm marker 0xFFDB (DQT)
    while i < n - 1:
        if data[i] == 0xFF and data[i+1] == 0xDB:
            # Đọc độ dài của segment DQT (2 bytes big-endian ngay sau marker)
            # length tính cả 2 byte độ dài và dữ liệu sau đó
            seg_len = (data[i+2] << 8) | data[i+3]

            # Bắt đầu phần payload (bỏ qua byte độ dài), tức index = i+4
            pos = i + 4
            end_of_seg = i + 2 + seg_len  # vị trí ngay sau segment DQT

            # Trong một segment DQT có thể có nhiều bảng (nhưng thường chỉ 1),
            # mỗi bảng bắt đầu bằng 1 byte Pq,Tq rồi 64 hoặc 128 byte dữ liệu:
            #   - Pq = high 4 bits: precision (0 => 8-bit, 1 => 16-bit)
            #   - Tq = low  4 bits: table ID
            # Nếu Pq=0, mỗi bảng có 1 (Pq/Tq) + 64 byte; nếu Pq=1 => 1+128 byte.
            while pos < end_of_seg:
                pq_tq = data[pos]
                precision = pq_tq >> 4       # 0 hoặc 1
                table_size = 64 * (1 + precision)

                # quant_data nằm từ pos+1 đến pos+table_size
                start_q = pos + 1
                end_q = start_q + table_size
                if end_q > end_of_seg:
                    # Nếu gặp lỗi về độ dài, thoát vòng này
                    break

                # Ghi đè toàn bộ quant_data thành 0x01
                data[start_q:end_q] = b'\x01' * table_size
                count += 1

                # Nhảy sang bảng tiếp theo (nếu có)
                pos = end_q

            # Nhảy qua toàn bộ segment DQT để tiếp tục tìm marker kế tiếp
            i = end_of_seg
        else:
            i += 1

    # Ghi file JPEG đã chỉnh sửa ra output_path
    with open(output_path, 'wb') as f:
        f.write(data)

    print(f"Đã xử lý {count} bảng quant và lưu thành '{output_path}'.")


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python3 replace_quant.py <input.jpg> <output.jpg>")
        sys.exit(1)

    inp = sys.argv[1]
    outp = sys.argv[2]
    fix_quant_tables(inp, outp)
```
![hehe](https://hackmd.io/_uploads/H1kO3WVXxl.jpg)
> Flag: tjctf{th3_Li0n_d03sNt_qUanT1ze}

---
## 7. forensics/thats-pietty-cool
![image](https://hackmd.io/_uploads/BybhhZEQgx.png)
![image](https://hackmd.io/_uploads/SJ8R2ZNQel.png)
```python=
from PIL import Image

orig_path = "runme.png"
orig = Image.open(orig_path)
width, height = orig.size

if orig.mode not in ("RGB", "RGBA"):
    orig = orig.convert("RGBA")

x_positions = list(range(0, 1845+1, 15))
y_positions = list(range(0, 1000+1, 100))

if not x_positions or not y_positions:
    raise RuntimeError("No pixels to sample (check image size vs. step size).")

new_w = len(x_positions)
new_h = len(y_positions)
mode = orig.mode
new_im = Image.new(mode, (new_w, new_h))

for j, y in enumerate(y_positions):
    for i, x in enumerate(x_positions):
        px = orig.getpixel((x, y))
        new_im.putpixel((i, j), px)

new_im.save("sampled_grid.png")
```
Script này lấy một ảnh đầu vào (runme.png), trích xuất các pixel tại các tọa độ cụ thể (theo lưới bước nhảy cố định), và tạo một ảnh mới (sampled_grid.png) với kích thước nhỏ hơn, chứa các pixel được lấy mẫu từ ảnh gốc.

![sampled_grid](https://hackmd.io/_uploads/BJN8a-4mgg.png)

Bỏ vào tool này:  
> https://www.bertnase.de/npiet/npiet-execute.php

![image](https://hackmd.io/_uploads/r1tFTb4Qxe.png)
> Flag: tjctf{p1et_pr1nt3r}

---
## 8. forensics/vr-keylog
![image](https://hackmd.io/_uploads/rJZ1JfEmxx.png)


---
## 9. misc/guess-my-number
![image](https://hackmd.io/_uploads/Sk5P1ME7lg.png)
```python=
import socket

# Create a socket and connect to the server
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('tjc.tf', 31700))

# Initialize the search range
low = 1
high = 1000

# Up to 10 guesses
for _ in range(10):
    # Calculate the midpoint for the guess
    guess = (low + high) // 2
    
    # Send the guess to the server (add newline as expected by input)
    sock.sendall(f"{guess}\n".encode())
    
    # Receive the server's response
    response = sock.recv(1024).decode().strip()
    
    # Check the response and adjust the search range
    if "Too high" in response:
        high = guess - 1
    elif "Too low" in response:
        low = guess + 1
    elif "You won" in response:
        print(response)  # Print the flag
        break
    else:
        print("Unexpected response:", response)
        break

# Close the socket
sock.close()
```
> Flag: You won, the flag is tjctf{g0od_j0b_u_gu33sed_correct_998}

---
## 10. misc/mouse-trail
![image](https://hackmd.io/_uploads/B1PcyzVmxl.png)
```python=
import matplotlib.pyplot as plt

def solve_mouse_trail(file_path):
    """
    Hàm này đọc file tọa độ, trực quan hóa chúng để tìm flag trong CTF.

    Args:
        file_path (str): Đường dẫn đến file txt chứa tọa độ.
    """
    x_coords = []
    y_coords = []

    print(f"Đang đọc tọa độ từ file: {file_path}...")
    try:
        # Bước 1: Đọc và phân tích dữ liệu tọa độ từ file
        with open(file_path, 'r') as f:
            for line in f:
                try:
                    # Tách chuỗi thành x, y và chuyển thành số nguyên
                    x, y = map(int, line.strip().split(','))
                    x_coords.append(x)
                    y_coords.append(y)
                except ValueError:
                    # Bỏ qua những dòng không hợp lệ
                    continue
        
        if not x_coords:
            print("Không tìm thấy tọa độ hợp lệ nào trong file.")
            return

        print("Đã đọc xong! Chuẩn bị vẽ hình...")

        # Bước 2: Tạo biểu đồ
        # figsize giúp cửa sổ hình ảnh đủ lớn để dễ nhìn
        plt.figure(figsize=(10, 6))

        # Vẽ các điểm bằng scatter plot.
        # s=1: Kích thước điểm rất nhỏ, giống như một pixel.
        # c='black': Màu đen để dễ nhìn trên nền trắng.
        plt.scatter(x_coords, y_coords, s=1, c='black')

        # Bước 3: Tinh chỉnh biểu đồ để flag hiện ra chính xác
        # Hầu hết các hệ tọa độ màn hình có gốc (0,0) ở góc trên bên trái.
        # Matplotlib mặc định gốc ở dưới trái, nên ta cần đảo ngược trục Y.
        plt.gca().invert_yaxis()

        # Đảm bảo tỉ lệ 2 trục bằng nhau để hình ảnh/chữ không bị méo
        plt.gca().set_aspect('equal', adjustable='box')
        
        # Thêm tiêu đề cho dễ hiểu
        plt.title('Mouse Trail Visualization - Find The Flag!')
        plt.xlabel('X Coordinate')
        plt.ylabel('Y Coordinate')

        # Bước 4: Hiển thị kết quả
        print("Xong! Cửa sổ hình ảnh đang được hiển thị...")
        plt.show()

    except FileNotFoundError:
        print(f"Lỗi: Không tìm thấy file '{file_path}'. Hãy chắc chắn file ở cùng thư mục.")
    except Exception as e:
        print(f"Đã xảy ra lỗi: {e}")

# --- Chạy hàm giải ---
# Đảm bảo file "mouse_movements.txt" của bạn ở cùng thư mục với file code này
solve_mouse_trail('mouse_movements.txt')
```
![image](https://hackmd.io/_uploads/rJWTkzEXge.png)

> Flag: tjctf{we_love_cartesian_plane}


---
## 11. misc/make-groups
![image](https://hackmd.io/_uploads/BkvJeGVXeg.png)
```python=
MOD = 998244353

# Precompute factorials and inverse factorials
def precompute_factorials(max_n, mod):
    factorial = [1] * (max_n + 1)
    inv_fact = [1] * (max_n + 1)

    for i in range(1, max_n + 1):
        factorial[i] = factorial[i - 1] * i % mod

    # Inverse using Fermat's Little Theorem
    inv_fact[max_n] = pow(factorial[max_n], mod - 2, mod)
    for i in range(max_n - 1, -1, -1):
        inv_fact[i] = inv_fact[i + 1] * (i + 1) % mod

    return factorial, inv_fact

def choose(n, r, fact, inv_fact, mod):
    if r > n: return 0
    return fact[n] * inv_fact[r] % mod * inv_fact[n - r] % mod

# Read input
with open("chall.txt") as f:
    n = int(f.readline().strip())
    a = list(map(int, f.readline().strip().split()))

# Compute result
fact, inv_fact = precompute_factorials(n, MOD)
ans = 1
for x in a:
    ans *= choose(n, x, fact, inv_fact, MOD)
    ans %= MOD

print(f"tjctf{{{ans}}}")
```
> Flag: tjctf{148042038}

---
## 12. misc/masked
![image](https://hackmd.io/_uploads/HkdrlG4Xgl.png)
![qrcode](https://hackmd.io/_uploads/r1e_gzNXxg.png)
Bruteforce...:  
```bash=
┌──(kali㉿kali)-[~/Desktop/tjctf]
└─$ python3 -m venv venv     
                                                                                                                                                           
┌──(kali㉿kali)-[~/Desktop/tjctf]
└─$ source venv/bin/activate
                                                                                                                                                           
┌──(venv)─(kali㉿kali)-[~/Desktop/tjctf]
└─$ pip install opencv-python
Collecting opencv-python
  Using cached opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (20 kB)
Collecting numpy>=1.21.2 (from opencv-python)
  Downloading numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (62 kB)
Using cached opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (63.0 MB)
Downloading numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl (16.6 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.6/16.6 MB 2.5 MB/s eta 0:00:00
Installing collected packages: numpy, opencv-python
Successfully installed numpy-2.3.0 opencv-python-4.11.0.86
                                                                                                                                                           
┌──(venv)─(kali㉿kali)-[~/Desktop/tjctf]
└─$ pip install pyzbar
Collecting pyzbar
  Using cached pyzbar-0.1.9-py2.py3-none-any.whl.metadata (10 kB)
Using cached pyzbar-0.1.9-py2.py3-none-any.whl (32 kB)
Installing collected packages: pyzbar
Successfully installed pyzbar-0.1.9
┌──(venv)─(kali㉿kali)-[~/Desktop/tjctf]
└─$ pip install pillow
Collecting pillow
  Using cached pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (8.9 kB)
Using cached pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl (4.6 MB)
Installing collected packages: pillow
Successfully installed pillow-11.2.1
```
```python=
#!/usr/bin/env python3

import sys
import itertools
import numpy as np
from PIL import Image

IMG_PATH = "qrcode.png"
try:
    img = Image.open(IMG_PATH).convert("L")
except FileNotFoundError:
    print(f"Error: Could not find '{IMG_PATH}'.")
    sys.exit(1)

img = img.point(lambda x: 0 if x < 128 else 255, "1")
img_np = np.array(img, dtype=np.uint8)
binary_img = (img_np == 0).astype(np.uint8)

h, w = binary_img.shape
if h != w:
    print("Error: Image is not square.")
    sys.exit(1)
SIZE = h

def find_module_size(size):
    for d in sorted([d for d in range(1, size + 1) if size % d == 0], reverse=True):
        count = size // d
        if 21 <= count <= 177 and (count - 21) % 4 == 0:
            return d, count
    return None, None

module_size, module_count = find_module_size(SIZE)
if module_size is None:
    print("Error: Cannot determine module size and count.")
    sys.exit(1)

print(f"Image {SIZE}x{SIZE}, module_size={module_size}, module_count={module_count}")

mod_grid = np.zeros((module_count, module_count), dtype=np.uint8)
for i in range(module_count):
    for j in range(module_count):
        block = binary_img[i*module_size:(i+1)*module_size, j*module_size:(j+1)*module_size]
        mod_grid[i, j] = 1 if block.sum() >= (module_size * module_size / 2) else 0

def create_functional_map(n):
    fmap = np.zeros((n, n), dtype=bool)
    
    for i in range(7):
        for j in range(7):
            fmap[i, j] = fmap[i, n-1-j] = fmap[n-1-i, j] = True
    
    for x in range(8):
        fmap[7, x] = fmap[x, 7] = True
        fmap[7, n-8+x] = fmap[x, n-8] = True
        fmap[n-8, x] = fmap[n-8+x, 7] = True
    
    for x in range(8, n-8):
        fmap[6, x] = fmap[x, 6] = True
    
    version = (n - 21) // 4
    if version >= 2:
        centers = [6, 4 * version + 10] if version > 5 else [6, [18, 22, 26, 30][version-2]]
        for cx in centers:
            for cy in centers:
                if (cx <= 8 and cy <= 8) or (cx <= 8 and cy >= n-8) or (cx >= n-8 and cy <= 8):
                    continue
                for di in range(-2, 3):
                    for dj in range(-2, 3):
                        if 0 <= cx + di < n and 0 <= cy + dj < n:
                            fmap[cx + di, cy + dj] = True
    
    for j in range(9):
        if j != 6:
            fmap[8, j] = True
    for i in range(9):
        if i != 6:
            fmap[i, 8] = True
    for j in range(n-8, n):
        fmap[8, j] = True
    for i in range(n-8, n):
        fmap[i, 8] = True
    
    return fmap

func_map = create_functional_map(module_count)

def apply_mask(i, j, mask_id):
    patterns = [
        (i + j) % 2 == 0,
        i % 2 == 0,
        j % 3 == 0,
        (i + j) % 3 == 0,
        ((i // 2) + (j // 3)) % 2 == 0,
        ((i * j) % 2 + (i * j) % 3) == 0,
        (((i * j) % 2 + (i * j) % 3) % 2) == 0,
        (((i + j) % 2 + (i * j) % 3) % 2) == 0
    ]
    return patterns[mask_id]

def unmask_grid(grid, fmap, mask_combo):
    n = grid.shape[0]
    result = grid.copy()
    for i in range(n):
        for j in range(n):
            if not fmap[i, j]:
                bit = sum(apply_mask(i, j, k) for k in mask_combo) % 2
                result[i, j] ^= bit
    return result

def grid_to_image(grid, msize):
    n = grid.shape[0]
    size = n * msize
    out = np.zeros((size, size), dtype=np.uint8)
    for i in range(n):
        for j in range(n):
            color = 0 if grid[i, j] else 255
            out[i*msize:(i+1)*msize, j*msize:(j+1)*msize] = color
    return out

try:
    from pyzbar.pyzbar import decode
    USE_PYZBAR = True
    print("Using PyZbar for QR decoding.")
except ImportError:
    import cv2
    USE_PYZBAR = False
    detector = cv2.QRCodeDetector()
    print("Using OpenCV QRCodeDetector.")

found = False
for r in range(8, 0, -1):
    for combo in itertools.combinations(range(8), r):
        unmasked = unmask_grid(mod_grid, func_map, combo)
        qr_img = grid_to_image(unmasked, module_size)
        
        if USE_PYZBAR:
            results = decode(qr_img)
            if results:
                for res in results:
                    text = res.data.decode("utf-8", errors="ignore")
                    print(f"Combo {combo}: {text}")
                    found = True
        else:
            data, _, _ = detector.detectAndDecode(qr_img)
            if data:
                print(f"Combo {combo}: {data}")
                found = True
        
        if found:
            break
    if found:
        break
```
![image](https://hackmd.io/_uploads/SyDZGG4mee.png)
> Flag: tjctf{n0tc4tchingc0vid}

---
## 13. misc/linkedout-recon
![image](https://hackmd.io/_uploads/HJ1EGfE7xe.png)
Osint:  
![image](https://hackmd.io/_uploads/rkRFfzEmeg.png)
![image](https://hackmd.io/_uploads/rykTzGNQeg.png)
![image](https://hackmd.io/_uploads/S1LCzzEQxx.png)
![image](https://hackmd.io/_uploads/HklUXz4Qgl.png)
> https://drive.google.com/file/d/1LAh1UUpHlfeagrN72dL_M9AsS8PRRGVz/view

![image](https://hackmd.io/_uploads/S1wDXf4Xel.png)
Tải về crack zip lấy pass, có ảnh:  
```bash=
┌──(kali㉿kali)-[~/Desktop/tjctf]
└─$ zsteg encoded.png 
b1,rgb,lsb,xy       .. text: "29:marmaduke:tjctf{linkedin_out}"
b2,r,lsb,xy         .. text: "QUeVAUie"
b2,bgr,lsb,xy       .. text: "M\r&MIBMI"
b2,rgba,lsb,xy      .. text: "k[7sssS'o'"
b3,g,lsb,xy         .. text: "Z%DJ) J%$"
b3,g,msb,xy         .. text: "mI\"-R %\n"
b3,b,msb,xy         .. file: OpenPGP Secret Key
b3,rgb,lsb,xy       .. file: Tower/XP rel 3 object
b4,b,msb,xy         .. text: "]=S=Y=U]Y"
```
> tjctf{linkedin_out}

---
