[Forensics] My Nervous PPT - Dreamhack

2025. 10. 12. 14:16·

Đề bài


Phân tích

Đầu tiên mình sẽ xem xem lưu lượng mạng từ file pcap này

Nhận thấy đây là các kết nối USB, vậy thì nó sẽ liên quan đến tương tác người dùng nhiều hơn

Đầu tiên sẽ là các gói tin ban đầu đại diện cho việc kết nối giữa USB và có thể là máy tính hoặc một thiết bị nào đó với GET/SET DESCRIPTOR hoặc SET CONFIGURATION

Protocol = USB, Info mô tả request/response chuẩn:

  • GET DESCRIPTOR Request DEVICE → host hỏi Device Descriptor (18 byte: bcdUSB, idVendor, idProduct, bMaxPacketSize0…)
  • GET DESCRIPTOR Response DEVICE → thiết bị trả descriptor DEVICE
  • GET DESCRIPTOR Request CONFIGURATION → host xin Configuration Descriptor (chuỗi: Configuration + Interface + (HID) + Endpoint…)
  • GET DESCRIPTOR Response CONFIGURATION → thiết bị trả block descriptor dài (wTotalLength, nên bạn thấy frame length lớn 46/87/228…)
  • SET CONFIGURATION Request/Response → host chọn cấu hình (thường là cấu hình 1) và thiết bị ACK (status stage)

Tiếp theo bên dưới sẽ là các gói tin thể hiện sự tương tác giữa người dùng và thiết bị

Những gói URB INTERRUPT là USB Request Block của hệ điều hành

  • Với HID (bàn phím/presenter), dữ liệu được truyền bằng Interrupt transfer trên endpoint IN (thiết bị → host) theo chu kỳ bInterval (host chủ động “polling”)
  • Vì là IN, mỗi “chu kỳ” thường tạo 2 bản ghi trong capture
    • URB_SUBMIT (source = host, dest = bus.dev.ep): host gửi yêu cầu đọc
    • URB_COMPLETE (source = bus.dev.ep, dest = host): thiết bị trả kết quả (có hoặc không có dữ liệu)

Còn với HID, HID là Keyboard/Presenter phổ biến, Input Report luôn 8 byte

[0] modifier | [1] reserved=0x00 | [2..7] là các keycode (tối đa 6 phím đang giữ)
  • modifier (byte 0): bitmask (0x01 LCtrl, 0x02 LShift, 0x04 LAlt, 0x08 LGUI, 0x10 RCtrl, 0x20 RShift, 0x40 RAlt, 0x80 RGUI)
  • reserved (byte 1): luôn 0x00
  • keycode (byte 2..7): mã HID theo Keyboard/Keypad Usage Page (0x07). 0x00 = không có phím tại vị trí đó

Trong bài này mình phát hiện HID chỉ có 3 trường hợp

  • 0200520000000000: 0x02 = Left Shift đang giữ; 0x52 = Up Arrow; còn lại 0 → không có phím khác
  • 0200510000000000: 0x02 = Left Shift đang giữ; 0x51 = Down Arrow; còn lại 0 → không có phím khác
  • 0200000000000000: 0x02 = Left Shift đang giữ; còn lại 0 → không có phím khác

Vậy nó sẽ rất giống với mã morse khi có 1 dài 1 ngắn, dựa trên ý tưởng này, mình tiếp tục phân tích tiếp


Trích xuất

Đầu tiên để phân tích thì mình sẽ trích xuất các HID từ gói tin pcap này ra

Sử dụng tshark để lấy ra hid.hex

$ tshark -r My_PPT.pcapng \
  -Y 'usbhid.data && usb.endpoint_address.direction==1 && usb.data_len==8' \
  -T fields -e usbhid.data > hid.hex

 

Kiểm tra xem đã lấy ra thành công chưa

$ cat hid.hex
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000
0200520000000000
0200000000000000
0200510000000000
0200000000000000
0200520000000000
0200000000000000

Vậy là đã trích xuất xong, bây giờ với suy đoán của mình thì khả năng 0x52 sẽ là dài, 0x51 sẽ là ngắn, tại vì 0x52 > 0x51 nên mình nghĩ vậy thôi

Script

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

EPB_TYPE = 0x00000006  # Enhanced Packet Block

MORSE = {
 ".-":"A","-...":"B","-.-.":"C","-..":"D",".":"E","..-.":"F","--.":"G","....":"H","..":"I",".---":"J",
 "-.-":"K",".-..":"L","--":"M","-.":"N","---":"O",".--.":"P","--.-":"Q",".-.":"R","...":"S","-":"T",
 "..-":"U","...-":"V",".--":"W","-..-":"X","-.--":"Y","--..":"Z",
 "-----":"0",".----":"1","..---":"2","...--":"3","....-":"4",".....":"5","-....":"6","--...":"7","---..":"8","----.":"9",
 ".-.-.-":".","--..--":",","..--..":"?","-.-.--":"!","-....-":"-","-..-.":"/",".--.-.":"@","-.--.":"(","-.--.-":")","..--.-":"_"
}

def read_pcapng_epbs(path):
    epbs = []
    data = open(path, "rb").read()
    pos, n = 0, len(data)
    while pos + 8 <= n:
        bt, bl = struct.unpack_from("<II", data, pos)
        if bl < 12 or pos + bl > n: break
        if bt == EPB_TYPE:
            # EPB fixed fields after type/len
            iface, ts_hi, ts_lo, cap_len, orig_len = struct.unpack_from("<IIIII", data, pos+8)
            pkt_start = pos + 8 + 20
            pkt_end   = pkt_start + cap_len
            if pkt_end <= pos + bl:
                epbs.append((ts_hi, ts_lo, data[pkt_start:pkt_end]))
        pos += bl
    return epbs

def decode_hid_candidates(pkt_bytes):
    """Scan for HID 8-byte report windows: [mod][res=0][k1..k6], >=1 key non-zero, small number of simultaneous keys."""
    cands = []
    L = len(pkt_bytes)
    for i in range(0, L-7):
        chunk = pkt_bytes[i:i+8]
        if chunk[1] != 0:  # reserved must be 0
            continue
        keys = chunk[2:]
        if not any(keys):
            continue
        # keep reports with up to 2 non-zero keys (reduce false positives)
        if sum(1 for k in keys if k != 0) > 2:
            continue
        cands.append(chunk)
    return cands

def keydown_events(epbs):
    """Emit key-down events (newly pressed HID codes) with timestamps."""
    prev_pressed = set()
    events = []
    for ts_hi, ts_lo, pkt in epbs:
        # Just scan raw payload for HID-like 8-byte windows
        for rep in decode_hid_candidates(pkt):
            mod = rep[0]
            keys = [k for k in rep[2:] if k != 0]
            cur  = set(keys)
            new_keys = [k for k in keys if k not in prev_pressed]
            t = ts_hi + ts_lo/1e9  # relative time ok
            for k in new_keys:
                events.append((t, k, mod))
            prev_pressed = cur
    return events

def decode_morse_from_arrows(events):
    """Use only UP/DOWN (0x52/0x51) with timestamps, infer unit timing, segment Morse, try two mappings."""
    # filter to arrows and sort by time
    arr = [(t, 'U' if k==0x52 else 'D') for (t,k,_) in events if k in (0x51,0x52)]
    arr.sort()
    if not arr: 
        return "", None, 0.05

    # build dt sequence
    seq = []
    for i,(t,sym) in enumerate(arr):
        dt = 0.0 if i==0 else max(0.0, t - arr[i-1][0])
        seq.append((sym, t, dt))

    # estimate unit from small non-zero gaps (10–40 percentile avg)
    nonzero = sorted([dt for _,_,dt in seq if dt>0])
    if nonzero:
        p10 = nonzero[int(0.10*len(nonzero))]
        p40 = nonzero[int(0.40*len(nonzero))]
        unit = (p10 + p40)/2 if p40>0 else (p10 or 0.05)
    else:
        unit = 0.05

    def decode_with(mapping_u_dot=True, intra=1.5, word=4.5):
        # mapping: True -> U='.', D='-'; False -> U='-', D='.'
        def gap(dt):
            if dt <= intra*unit: return 'intra'
            if dt <= word*unit:  return 'letter'
            return 'word'
        blocks, cur = [], []
        for i,(sym, _, dt) in enumerate(seq):
            ch = ('.' if mapping_u_dot else '-') if sym=='U' else ('-' if mapping_u_dot else '.')
            cur.append(ch)
            if i+1 < len(seq):
                g = gap(seq[i+1][2])
                if g=='letter': blocks.append("".join(cur)); cur=[]
                elif g=='word': blocks.append("".join(cur)); blocks.append(" "); cur=[]
        if cur: blocks.append("".join(cur))
        text = "".join(" " if b==" " else MORSE.get(b, "?") for b in blocks)
        return text, blocks

    # try both mappings; pick fewer '?' (tie-breaker: prefers 'KHK','LOOK','PPT','AT','MY')
    t1,_ = decode_with(mapping_u_dot=True)
    t2,_ = decode_with(mapping_u_dot=False)
    def score(tx): return -5*tx.count("?") + 3*tx.count("KHK") + 2*tx.count("LOOK") + 2*tx.count("PPT") + tx.count("AT") + tx.count("MY")
    best = (t1, True) if score(t1) >= score(t2) else (t2, False)
    return best[0], best[1], unit

def normalize_flag(text):
    # collapse spaces, map () -> {} then look for KHK{...}
    norm = "".join(text.split()).replace("(", "{").replace(")", "}")
    m = re.search(r"KHK\{[A-Z0-9_]+\}", norm.upper())
    return m.group(0) if m else None

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 usb_presenter_morse_solver.py <file.pcapng>")
        sys.exit(1)

    epbs = read_pcapng_epbs(sys.argv[1])
    events = keydown_events(epbs)
    text, u_is_dot, unit = decode_morse_from_arrows(events)

    print(f"[*] arrow-events: {sum(1 for e in events if e[1] in (0x51,0x52))}, unit≈{unit:.4f}s, mapping: {'U=.' if u_is_dot else 'U=-'}")
    print("[*] decode:", text)
    flag = normalize_flag(text)
    if flag:
        print("[*] flag:", flag)

if __name__ == "__main__":
    main()

Khi chạy ra thì kết quả khá xấu, có vẻ là đề cố tình làm nhiễu

$ python3 solve.py My_PPT.pcapng
[*] arrow-events: 70, unit≈0.0006s, mapping: U=-
[*] decode: K H K ( L O O K IQ A T IQ M Y _ EG P T T?

Flag

Flag: KHK{LOOK_AT_MY_PPT}

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

[Forensics] flask-forensics - Dreamhack  (0) 2025.10.12
[Forensics] abcdefg-who - Dreamhack  (0) 2025.10.12
[Forensics] Dream Zoo - Dreamhack  (0) 2025.10.11
[Forensics] Silent Visitor - Securinets CTF Quals 2025  (0) 2025.10.06
[Forensics] Remotely Interesting (SunshineCTF 2025)  (0) 2025.09.30
'WriteUp/Forensics' Other posts in category
  • [Forensics] flask-forensics - Dreamhack
  • [Forensics] abcdefg-who - Dreamhack
  • [Forensics] Dream Zoo - Dreamhack
  • [Forensics] Silent Visitor - Securinets CTF Quals 2025
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

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

  • Recent Posts

  • hELLO· Designed ByLong.v4.10.4
longhd
[Forensics] My Nervous PPT - Dreamhack
Go to Top

티스토리툴바