# TJCTF 2025 [FOR+MISC]

## 1. forensics/hidden-message



> Flag: tjctf{steganography_is_fun}
---
## 2. forensics/deep-layers



Binwalk ra:

Giải nén với pass đã cho:

> Flag: tjctf{p0lygl0t_r3bb1t_h0l3}
---
## 3. forensics/footprint

> https://github.com/gehaxelt/Python-dsstore

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

```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')
```

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())
```

> Flag: tjctf{THIS-EASTER-EGG-IS-PRETTY-COOL}
---
## 5. forensics/packet-palette

```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")
```

> Flag: tjctf{usb1p_f13g_1ns1d3_3_pr0t0c0l}
---
## 6. forensics/quant


```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)
```

> Flag: tjctf{th3_Li0n_d03sNt_qUanT1ze}
---
## 7. forensics/thats-pietty-cool


```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.

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

> Flag: tjctf{p1et_pr1nt3r}
---
## 8. forensics/vr-keylog

---
## 9. misc/guess-my-number

```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

```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')
```

> Flag: tjctf{we_love_cartesian_plane}
---
## 11. misc/make-groups

```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


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
```

> Flag: tjctf{n0tc4tchingc0vid}
---
## 13. misc/linkedout-recon

Osint:




> https://drive.google.com/file/d/1LAh1UUpHlfeagrN72dL_M9AsS8PRRGVz/view

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}
---