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