Forensics - EHAX CTF 2026

2026. 3. 2. 00:02·

baby serial


let-the-penguin-live

Kiểm tra thông tin file mkv đề cho

ffprobe -hide_banner challenge.mkv
Input #0, matroska,webm, from 'challenge.mkv':
  Metadata:
    title           : Penguin
    COMMENT         : EH4X{k33p_try1ng}
    MAJOR_BRAND     : isom
    MINOR_VERSION   : 512
    COMPATIBLE_BRANDS: isomiso2avc1mp41
    ENCODER         : Lavf62.3.100
  Duration: 00:01:03.02, start: 0.000000, bitrate: 1021 kb/s
  Stream #0:0: Video: h264 (High), yuv420p(tv, bt709, progressive), 576x320 [SAR 1:1 DAR 9:5], 23.98 fps, 23.98 tbr, 1k tbn (default)
    Metadata:
      HANDLER_NAME    : ISO Media file produced by Google Inc.
      VENDOR_ID       : [0][0][0][0]
      ENCODER         : Lavc62.11.100 libx264
      DURATION        : 00:01:03.022000000
  Stream #0:1: Audio: flac, 44100 Hz, stereo, s16 (default)
    Metadata:
      title           : English (Stereo)
      ENCODER         : Lavc62.11.100 flac
      DURATION        : 00:01:03.019000000
  Stream #0:2: Audio: flac, 44100 Hz, stereo, s16
    Metadata:
      title           : English (5.1 Surround)
      ENCODER         : Lavc62.11.100 flac
      DURATION        : 00:01:03.019000000

Thấy nó chứa 2 file audio, trích xuất ra

ffmpeg -i challenge.mkv -map 0:1 -c copy audio_stereo.flac
ffmpeg -i challenge.mkv -map 0:2 -c copy audio_surround.flac

Ở đây thì mình đã nghe qua 2 file audio và thấy chúng không có điểm gì khác biệt lắm về mặt nội dung (đều nói về cùng nội dung trong vieo), có một file dài hơn file còn lại khoảng 1 giây nên mình đã nghi điểm khác biệt về mặt âm thanh sẽ là flag

Lấy ra điểm khác biệt giữa 2 file

ffmpeg -y -i audio_stereo.flac -i audio_surround.flac -filter_complex "amix=inputs=2:weights='1 -1'" -ss 00:00:00.0 -to 00:01:03.0 diff.flac

Sau đó mình sẽ xem quang phổ của nó

Flag: EH4X{0n3_tr4ck_m1nd_tw0_tr4ck_f1les}


painter

Bài này sẽ cho mình một file pcap USB

File pcap này sẽ bắt chuyển động của chuột

Chuột sẽ gửi:

  • dx (int16)
  • dy (int16)

Khôi phục thao tác chuột

import struct, math, os
import numpy as np
import cv2

pcap_path="pref.pcap"
data=open(pcap_path,"rb").read()

def u32le(b,o): return struct.unpack_from("<I",b,o)[0]
def u16le(b,o): return struct.unpack_from("<H",b,o)[0]
def u64le(b,o): return struct.unpack_from("<Q",b,o)[0]

# Minimal pcapng reader: pull Enhanced Packet Blocks
packets=[]
off=0
while off+8<=len(data):
    btype=u32le(data,off)
    blen=u32le(data,off+4)
    if blen<12 or off+blen>len(data):
        break
    body=data[off+8:off+blen-4]
    if btype==0x00000006 and len(body)>=20:  # EPB
        caplen=u32le(body,12)
        pkt=body[20:20+caplen]
        packets.append(pkt)
    off+=blen

# DLT_USB_LINUX_MMAPPED (220): 64-byte header, then data_len bytes
coords=[]
x=y=0
deltas=[]
for pkt in packets:
    if len(pkt)<64:
        continue
    dlen=u32le(pkt,36)
    if 64+dlen>len(pkt):
        continue
    pl=pkt[64:64+dlen]
    if len(pl)<6:
        continue
    dx=struct.unpack_from("<h",pl,2)[0]
    dy=struct.unpack_from("<h",pl,4)[0]
    x+=dx; y+=dy
    coords.append((x,y))
    deltas.append((dx,dy))

xy=np.array(coords, dtype=np.float64)
# PCA deskew angle
pts_center=xy-xy.mean(axis=0)
cov=np.cov(pts_center.T)
eigvals,eigvecs=np.linalg.eig(cov)
v=eigvecs[:, np.argsort(eigvals)[::-1][0]]
angle=math.atan2(v[1], v[0])  # radians
theta=-angle
R=np.array([[math.cos(theta), -math.sin(theta)],
            [math.sin(theta),  math.cos(theta)]])
rot=(pts_center @ R.T)

# Split stroke on fast cursor moves to reduce connecting lines
step=np.hypot(np.diff(rot[:,0]), np.diff(rot[:,1]))
thresh=np.percentile(step, 99)  # cut only the fastest 1%
segs=[]
cur=[rot[0]]
for i in range(1,len(rot)):
    if math.hypot(rot[i,0]-rot[i-1,0], rot[i,1]-rot[i-1,1])>thresh:
        if len(cur)>1: segs.append(np.array(cur))
        cur=[rot[i]]
    else:
        cur.append(rot[i])
if len(cur)>1: segs.append(np.array(cur))

allp=np.vstack(segs)
minx,miny=allp.min(axis=0); maxx,maxy=allp.max(axis=0)
pad=60
W=int((maxx-minx)+2*pad); H=int((maxy-miny)+2*pad)
canvas=np.ones((H,W), dtype=np.uint8)*255
for s in segs:
    xs=(s[:,0]-minx+pad).astype(np.int32)
    ys=(maxy-s[:,1]+pad).astype(np.int32)  # invert Y for display
    pts=np.stack([xs,ys],axis=1).reshape((-1,1,2))
    cv2.polylines(canvas,[pts],False,0,2,lineType=cv2.LINE_AA)

out_path="recovered_drawing.png"
cv2.imwrite(out_path, canvas)

out_path, len(packets), len(coords), thresh

Mình sẽ thu được ảnh sau

Mirror ảnh lại thì sẽ thu được

Flag: EH4X{Wh4t_c0l0ur_15_th3_fl4g}


power leak

 

Thiết bị đang kiểm tra secret theo từng vị trí (position). Với mỗi position, hệ thống thử một guess (0-9). Khi guess đúng hoặc sai, chương trình đi qua đường code khác nhau (hoặc thực hiện phép tính khác), nên công suất tiêu thụ khác nhau tại một vài thời điểm

Dataset cho nhiều lần đo:

  • 20 traces cho cùng một guess → để lấy trung bình giảm nhiễu (noise)
  • 50 samples trong mỗi trace → power theo thời gian

Trong file này, phần lớn samples chỉ là baseline ~ như nhau. Nhưng có một khoảng ngắn (ở đây là sample ~20–21) mà:

  • các guess tạo ra khác biệt rõ rệt
  • và guess đúng thường tạo “đỉnh” (hoặc hình dạng) khác

Cho từng position:

  1. Group theo guess (0..9)
  2. Với mỗi guess, average 20 traces → ra “mean trace” ổn định
  3. Tìm “leak point” t:
    • đo variance giữa 10 guess tại từng sample
    • sample nào variance lớn → sample đó phân biệt guess mạnh nhất
  4. Ở sample leak đó, chọn guess có mean power lớn nhất (hoặc nhỏ nhất tùy bài) → ra digit của secret

Script

import pandas as pd
import numpy as np
import hashlib

df = pd.read_csv("power_traces.csv").sort_values(
    ["position", "guess", "trace_num", "sample"]
)

# (pos=6, guess=10, trace=20, sample=50)
arr = df["power_mW"].to_numpy().reshape((6, 10, 20, 50))

def between_guess_var_at(pos, t):
    # mean over traces for each guess at sample t
    mu = arr[pos, :, :, t].mean(axis=1)  # (10,)
    return float(mu.var(ddof=1)), mu

digits = []
for pos in range(6):
    v20, mu20 = between_guess_var_at(pos, 20)
    v21, mu21 = between_guess_var_at(pos, 21)

    if v20 >= v21:
        digit = int(np.argmax(mu20))
    else:
        digit = int(np.argmax(mu21))

    digits.append(str(digit))

secret = "".join(digits)
h = hashlib.sha256(secret.encode()).hexdigest()
print("secret =", secret)
print("flag   =", f"EHAX{{{h}}}")

Flag: EHAX{5bec84ad039e23fcd51d331e662e27be15542ca83fd8ef4d6c5e5a8ad614a54d}


Quantum Message

Bài này cho mình một file âm thanh, đầu tiên vẫn sẽ là kiểm tra quang phổ trước

Sau một hồi tìm kiếm thì mình thấy đây là dạng DTMF

Script giải mã

import numpy as np
import scipy.io.wavfile as wav
from itertools import groupby
import math

path = "challenge.wav"
sr, x = wav.read(path)
x = x.astype(np.float32)
if x.ndim > 1:
    x = x[:, 0]

# Custom DTMF-like frequency bins (đúng với file này)
low_cent  = np.array([301.5, 904.3, 1501.9, 2104.9], dtype=np.float32)
high_cent = np.array([2702.4, 3305.3, 3908.2], dtype=np.float32)

# STFT params
win = 8192
hop = 1024
window = np.hanning(win).astype(np.float32)
freqs = np.fft.rfftfreq(win, 1/sr)
mask = (freqs > 50) & (freqs < 5000)
fi = freqs[mask]

def nearest(val, arr):
    idx = int(np.argmin(np.abs(arr - val)))
    return idx

symbols = []
times = []
for start in range(0, len(x) - win, hop):
    frame = x[start:start+win] * window
    spec = np.abs(np.fft.rfft(frame))[mask]

    top = np.argpartition(spec, -10)[-10:]
    top = top[np.argsort(spec[top])[::-1]]

    selected = []
    for idx in top:
        f = float(fi[idx])
        if all(abs(f - s) > 40 for s in selected):
            selected.append(f)
        if len(selected) == 2:
            break
    if len(selected) != 2:
        continue

    a, b = sorted(selected)
    if b < 2400:
        continue

    r = nearest(a, low_cent)
    c = nearest(b, high_cent)
    symbols.append((r, c))
    times.append(start / sr)

# RLE theo symbol change
rle = []
for sym, group in groupby(zip(symbols, times), key=lambda st: st[0]):
    g = list(group)
    t0 = g[0][1]
    t1 = g[-1][1] + hop/sr
    rle.append((sym, t0, t1, t1 - t0))

# Ước lượng độ dài 1 "phím"
unit = np.median([d for _,_,_,d in rle])

# Expand các block dài (do lặp số) thành nhiều phím
expanded = []
for sym, t0, t1, d in rle:
    n = max(1, int(round(d / unit)))
    expanded.extend([sym] * n)

# Keypad mapping (giống DTMF chuẩn, nhưng tần số custom)
digit_map = {}
d = 1
for r in range(3):
    for c in range(3):
        digit_map[(r, c)] = str(d); d += 1
digit_map[(3, 1)] = "0"

digits = "".join(digit_map[s] for s in expanded)

# Parse ASCII: chỉ cho phép 2 hoặc 3 chữ số / ký tự printable
from functools import lru_cache
@lru_cache(None)
def rec(i):
    if i == len(digits):
        return [""]
    out = []
    for L in (2, 3):
        if i + L <= len(digits):
            v = int(digits[i:i+L])
            if 32 <= v <= 126:
                for tail in rec(i + L):
                    out.append(chr(v) + tail)
    return out

sol = rec(0)[0]
print(sol)

Flag: EH4X{qu4ntum_phys1c5_15_50_5c4ry}


Jpeg Soul

Bài này stego theo dạng trích tất cả giá trị trong các bảng DQT, lấy bit cuối (value & 1), ghép thành bytes thì ra flag

Script

#!/usr/bin/env python3
import struct, re

ZIGZAG = [
 0, 1, 8,16, 9, 2, 3,10,
17,24,32,25,18,11, 4, 5,
12,19,26,33,40,48,41,34,
27,20,13, 6, 7,14,21,28,
35,42,49,56,57,50,43,36,
29,22,15,23,30,37,44,51,
58,59,52,45,38,31,39,46,
53,60,61,54,47,55,62,63
]

def unzigzag(q64):
    out = [0]*64
    for pos, natural_idx in enumerate(ZIGZAG):
        out[natural_idx] = q64[pos]
    return out

def extract_all_dqt_tables(jpeg: bytes):
    assert jpeg[:2] == b"\xff\xd8"
    i = 2
    tables = []
    while i < len(jpeg):
        if jpeg[i] != 0xFF:
            j = jpeg.find(b"\xff", i)
            if j < 0: break
            i = j
        while i < len(jpeg) and jpeg[i] == 0xFF:
            i += 1
        if i >= len(jpeg): break

        marker = jpeg[i]; i += 1
        if marker == 0xDA:  # SOS
            break
        if marker in (0xD8, 0xD9) or (0xD0 <= marker <= 0xD7):
            continue

        seglen = struct.unpack(">H", jpeg[i:i+2])[0]; i += 2
        seg = jpeg[i:i+seglen-2]; i += seglen-2

        if marker == 0xDB:  # DQT
            j = 0
            while j < len(seg):
                pq_tq = seg[j]; j += 1
                pq = pq_tq >> 4
                if pq != 0:
                    raise ValueError("Only 8-bit DQT supported here (Pq=0).")
                q = list(seg[j:j+64]); j += 64
                tables.append(q)
    return tables

def bits_to_bytes(bits):
    out = bytearray()
    for k in range(0, len(bits), 8):
        chunk = bits[k:k+8]
        if len(chunk) < 8: break
        b = 0
        for bit in chunk:   # MSB-first
            b = (b << 1) | bit
        out.append(b)
    return bytes(out)

jpeg = open("soul.jpg", "rb").read()
tables = extract_all_dqt_tables(jpeg)

vals = []
for t in tables:
    vals.extend(unzigzag(t))          # <<< điểm khác biệt: un-zigzag rồi mới đọc

bits = [v & 1 for v in vals]
blob = bits_to_bytes(bits)

print(blob.decode(errors="replace"))

Flag: EHAX{jp3g_s3crt}

'WriteUp > Forensics' 카테고리의 다른 글

Forensics - ESCHATON CTF Quals 2026  (0) 2026.03.01
Forensics - VSL CTF 2026  (0) 2026.01.26
Báo cáo dang dở - Cookie Arena  (0) 2025.11.22
Under Control - Cookie Arena  (0) 2025.11.22
Masks Off - HackTheBox  (0) 2025.11.21
'WriteUp/Forensics' Other posts in category
  • Forensics - ESCHATON CTF Quals 2026
  • Forensics - VSL CTF 2026
  • Báo cáo dang dở - Cookie Arena
  • Under Control - Cookie Arena
longhd
longhd
Longhd's Blog
  • longhd
    Ha Duy Long - InfosecPTIT
    longhd
  • Total
    Today
    Yesterday
  • About me

    • Hello I'm Duy Long 👋🏻
    • View all categories (117) N
      • Certificates (4)
      • CTF (3)
      • WriteUp (94) N
        • Forensics (44) N
        • Steganography (5)
        • RE (9) N
        • OSINT (8)
        • Web (17)
        • MISC (6)
        • Crypto (3)
        • Pwn (2)
      • Love Story (0)
      • Labs (15)
        • Information Gathering (10)
        • Vulnerability Scanning (2)
        • Introduction to Web Applica.. (1)
        • Common Web Application Atta.. (1)
        • SQL Injection Attacks (1)
  • Blog Menu

    • Home
    • Tag
    • GuestBook
  • Popular Posts

  • Tags

    writeup
    EnigmaXplore3.0
    THM
    OSINT
    POCCTF2025
    Re
    CHH
    Dreamhack
    CSCV2025
    Web
    CTF
    Steganography
    V1tCTF2025
    htb
    PTITCTF2025
    SunshineCTF2025
    Forensics
    picoCTF
    BuckeyeCTF2025
    misc
  • Recent Comments

  • Recent Posts

  • hELLO· Designed ByLong.v4.10.4
longhd
Forensics - EHAX CTF 2026
Go to Top

티스토리툴바