# MTA CTF 2024
Challenge: tổng cộng có 6. <a href="https://github.com/0xMikiko/CTF-Challenge-Storage">Hãy tải đề tại đây.</a> Flag format: MSEC{XXXXXXXXXX}.
<ul>
<li><a href="#Flag Checker">1. Flag Checker</a></li>
<li><a href="#Baby Kernel">2. Baby Kernel</a></li>
<li><a href="#Electron Cat">3. Electron Cat</a></li>
<li><a href="#Truyện cười Remind 3">4. Truyện cười Remind 3</a></li>
<li><a href="#Ready to run 3000m">5. Ready to run 3000m</a></li>
<li><a href="#Mustang Panda">6. Mustang Panda</a></li>
</ul>
<div id="Flag Checker"></div>
### 1. Flag Checker
Mở chương trình vơi DIE.

Mở chương trình với IDA. Ta sẽ phân tích hàm main().

Chương trình cố gắng tìm tài nguyên có `ID = 0x65 và type = RC Data` và tải tài nguyên đó. Sau đó tiến hành giải mã bằng phép XOR với chuỗi `ILOVEMSEC`. Vậy tài nguyên này ở đâu?
Ta mở lại tệp với DIE và vào Resource. Ta sẽ tìm thấy được Resource được dùng để giải mã.

Ta giải mã và biết được mở đầu nó là "MZ" - DOS Header nên ta sẽ lưu nó vào một tệp.
:::info
```python
import ida_bytes
start_addr = 0x000001E020B5F880
length = 0x10800
value = ""
for i in range(start_addr, start_addr + length):
tmp = ida_bytes.get_byte(i)
value += hex(tmp)[2:].zfill(2)
with open("server.exe", "wb") as f:
f.write(bytes.fromhex(value))
```
:::
Sau đó chương trình thực hiện vô hạn giao tiếp với pipe cho đến khi người dùng nhập đúng "flag" hoặc đạt đến số lần thử tối đa. Dữ liệu mà người dùng nhập được gửi qua pipe bằng hàm WriteFile. Sau đó, chương trình đọc phản hồi từ máy chủ qua pipe bằng hàm ReadFile và lưu trữ trong serverResponse.
Chương trình kiểm tra xem phản hồi từ máy chủ có khớp với chuỗi mong đợi (trong trường hợp này là "Success"). Nếu khớp, chương trình in ra "Server response" và kết thúc. Nếu không khớp, nó sẽ yêu cầu người dùng thử lại.


Hàm sub_7FF75E001070() sử dụng CreateProcessA với lệnh nslookup.exe. Quy trình mới được tạo ra ở trạng thái bị treo để có thể thực hiện thao tác tiếp theo trước khi cho quy trình này chạy. Ghi Đè và sửa đổi bộ nhớ của tiến trình mới, ghi đè các tham chiếu bộ nhớ trong tiến trình mới và thiết lập lại thread context để quá trình mới có thể chạy từ vùng đã chỉnh sửa.
Ta phân tích tệp server.exe vừa được tạo. Ta tìm thấy thuật toán mã hóa AES.

Từ đây, ta cần tìm: key, IV, cipher. Ta thấy hàm sử dụng a1 làm key và a2 làm IV.


Tiếp tục tìm kiếm ta sẽ thấy cipher.

Tiến hành tìm IV và KEY
:::info
```python
data = bytes([0x1A, 0x16, 0x09, 0x0C, 0x44, 0x55, 0x44, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x46, 0x55, 0x53, 0x59])
key = b"msec2024"
result = bytes(a ^ b for a, b in zip(data, key * (len(data) // len(key) + 1)))
print("XOR Result (Hex):", result.hex())
# IV = KEY = welovevnmsecteam
```
:::
Mở cyberchef lên và giải mã để tìm đầu vào.

:::success
```
Flag: MSEC{W3_c0mmun1c4t3_v1a_IPC!}
```
:::
<div id="Baby Kernel"></div>
### 2. Baby Kernel
Ta được cung cấp các tệp sau:
* MsecClient.exe
* mseckernel.cat
* MSECKernel.inf
* MsecKernel.sys
Tệp `MSECKernel.inf` cài đặt và cấu hình một Driver trên Windows, bao gồm dịch vụ liên quan, các khóa registry cần thiết, và các tệp cần sao chép vào hệ thống. Ta tiến hành cài đặt Driver `MSECKernel.inf` . Lưu ý: nhớ chạy driver vừa cài đặt.
```!
******************* Các thành phần chính của Window Driver *******************
Driver Entry: Đây là điểm vào (entry point) của driver, tương tự như hàm main trong các ứng dụng thông thường. Nó được gọi khi driver được tải vào hệ thống.
IRP (I/O Request Packet): Đây là một cấu trúc dữ liệu mà hệ điều hành sử dụng để giao tiếp với driver. Khi có một yêu cầu I/O (đọc/ghi), hệ điều hành sẽ tạo một IRP và gửi nó đến driver tương ứng.
Dispatch Routines: Đây là các hàm trong driver xử lý các IRP khác nhau, ví dụ: đọc dữ liệu, ghi dữ liệu, xử lý các lệnh điều khiển thiết bị (Device Control).
```
```!
******************* Các hàm quan trọng trong Windows Driver *******************
IoCreateDevice: Dùng để tạo một đối tượng thiết bị (Device Object) cho driver, giúp driver tương tác với các ứng dụng người dùng hoặc với phần cứng.
IoDeleteDevice: Dùng để xóa một đối tượng thiết bị khi driver không còn cần sử dụng nữa.
IoCreateSymbolicLink: Tạo một liên kết tượng trưng giữa tên thiết bị và tên symbolic link, cho phép các ứng dụng người dùng giao tiếp với driver.
DeviceIoControl: Cho phép ứng dụng người dùng gửi các lệnh điều khiển tới driver
```
Mở `MsecClient.exe` trong IDA. Chương trình yêu cầu người dùng truyền tham số là một số. Tạo đối tượng kết nối Driver có tên "D3vkn1ght". DeviceIoControl được gọi để gửi lệnh điều khiển đến thiết bị, OutBuffer là buffer chứa dữ liệu gửi đi và nhận về. Ta cần phải vượt qua các yêu cầu để có cipher để giải mã.

Mở `MsecClient.exe` trong IDA. Chương trình thiết lập các hàm xử lý:
* `DriverUnload`: Thiết lập hàm sẽ được gọi khi driver bị dỡ khỏi hệ thống.
* `MajorFunction[0] và MajorFunction[2]`: Thiết lập hàm xử lý khi thiết bị được mở (IRP_MJ_CREATE) hoặc đóng (IRP_MJ_CLOSE).
* `MajorFunction[14]`: Thiết lập hàm xử lý các lệnh điều khiển thiết bị (IRP_MJ_DEVICE_CONTROL).
Biến INIT có giá trị ban đầu là 1234. Hàm DispatchCreateClose() gán giá trị INIT là 2024. Cập nhật trạng thái của IRP thành STATUS_SUCCESS (0).

Hàm DispatchDeviceControl() kiểm tra lệnh điều khiển và tham số truyền vào. Ta có thể nhận thấy giá trị `&MasterIrp->Size + 1` tương đương với giá trị `Case` nhận được ở Client.
* Nếu `MasterIrp->Type == INIT` (với INIT = 2024) thì `&MasterIrp->Size + 1 = 1`
* Nếu giá trị `KEY != NULL` thì `&MasterIrp->Size + 1 = 2`
* Dùng KEY[i] ^ `MasterIrp->Type` vào gắn `&MasterIrp->Size + 1 = 3`
Hàm PnpNotificationCallback() xử lý các thông báo Plug and Play. Chương trình xử lý sự kiện khi một thiết bị kết nối hoặc gỡ bỏ. Được tác giá hướng dẫn rồi nhưng vẫn đi tìm struct của nó để dễ đọc hơn. Hãy vào LocalType của IDA để thêm. <a href ="https://doxygen.reactos.org/d8/d0d/drivers_2wdm_2audio_2sysaudio_2deviface_8c.html">Đọc thêm tại đây để hiểu lý do</a>
:::info
```
typedef struct DEVICE_INTERFACE_CHANGE_NOTIFICATION {
USHORT Version;
USHORT Size;
GUID Event;
GUID InterfaceClassGuid;
PUNICODE_STRING SymbolicLinkName;
}
```
:::

Khi một thiết bị được kết nối, KEY = SymbolicLink[6]. Ta cắm các thiết bị USB Controller là được. SymbolicLink có mở đầu là ``\??\USBSTOR#<DeviceType>&<Producer>&<Product>&....``
* `\??\`: Tiền tố đường dẫn thiết bị trong Windows
* `USBSTOR`: Thiết bị lưu trữ USB.

Sau khi có được KEY = `\??\` thì nó sẽ XOR LOBYTE(2024) tạo thành mảng 6 phần tử và gửi dữ liệu về để tiến hành giải mã. Ta không cần viết đoạn mã để giải mã, chỉ cần thực thi là được. Thiết bị được cắm vào có thể gồm: USB, HDD, SSD, điện thoại, ...

:::success
```
Flag: MSEC{D3vic3_P1ugPl4y_N0t1fy_M4st3r}
```
:::
<div id="Electron Cat"></div>
### 3. Electron Cat
Ta nhận được một tệp `catelectron.exe`, khi thực thi nó sẽ cài đặt một tệp `catelectron.exe` khác có kích thước lớn hơn. Mình tiến hành giải nén nó bằng 7z và nhận được hai folder:
* $R0: chứa Uninstall catelectron.exe
* $PLUGINSDIR: tệp `catelectron.exe` thật và toàn bộ các thư viện kèm theo. Trong đó nổi bật là từ khóa `electron`.
```!
Electron Apps là ứng dụng đa nền tảng được xây dựng bằng các công nghệ web như JS, HTML và CSS, chạy trên nền tảng trình duyệt Chromium được đơn giản hóa cùng với Node.JS.
Trong Electron, file .asar là một dạng tệp lưu trữ (archive) được sử dụng để gói gọn các tài nguyên của ứng dụng Electron, bao gồm mã nguồn JavaScript, HTML, CSS, và các tài nguyên khác như hình ảnh, phông chữ. .asar là viết tắt của "Atom Shell Archive," và nó được thiết kế để hỗ trợ Electron, một framework cho phép phát triển ứng dụng desktop bằng cách sử dụng các công nghệ web.
```
Ta có thể lấy mã nguồn của bất kỳ Electron Apps nào, chỉ cần tìm thấy tệp .asar. Sử dụng các lệnh sau để lấy mã nguồn của nó.
```!
npm install -g asar
asar extract app.asar <folder_name>
```
Ta nhận được 3 folder và 4 file JavaScript. Tới đây chỉ cần phân tích đoạn mã JS là xong. Tóm tắt là nó lấy cipher từ Google Spreadsheet đưa vào hàm giải mã, so sánh kết quả với dữ liệu nhập vào và đưa ra thông báo.

Ta tổng hợp đoạn mã của các file JS và thực thi nó.
:::info
```javascript
const axios = require('axios');
const crypto = require('crypto');
const SHEET_ID = '1BPitVV-Sjf-zXgNX1mD3dCDKCE0z9Z0J_i5j8tq1up4';
const RANGE = 'Sheet1!A15';
const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxR2b4QcM5o3TiQdksU4V
J5mKGK4dV+VG7I4LT40ystlhB+z/FuqcIDnKf/tafi9ucVeh5QzzcnHI25CR1QWN
E4Rd3WR1LN6aQUAJqInUEq23/MVMYRnGzwT+s/lXC3SyKd4TJ7rly7OaFXsBCi4d
Bu2B+PpaKUZxPcekWUHiK6/hbf2lGhPYH63veyCMTeY+F5dAzqhpki/Me1ZwEMdE
i/BmY7Y8LobdFC6HqWSxezl7EEXXNC/bzIf4wHquaUWdb19NBRq9ZikdceamSREG
t5ZzVVS3z8hzKhB+DsQkAMq1MVqljTUqmmBJgBELWZ0//oOPBQJCYh0fBUwDAzkB
jQIDAQAB
-----END PUBLIC KEY-----`;
async function getFlag() {
const url = `https://docs.google.com/spreadsheets/d/${SHEET_ID}/gviz/tq?tqx=out:json&range=${RANGE}`;
try {
const response = await axios.get(url);
return JSON.parse(response.data.substr(47).slice(0, -2)).table.rows[0].c[0].v;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
}
function decryptKey(encryptedKey) {
return Buffer.concat(
Buffer.from(encryptedKey, 'hex')
.reduce((chunks, _, i, buffer) =>
i % 256 ? chunks : [...chunks, crypto.publicDecrypt(publicKey, buffer.slice(i, i + 256))], [])
).toString('utf-8');
}
(async () => {
try {
const encryptedFlag = await getFlag();
if (encryptedFlag) {
console.log('Flag:', JSON.parse(decryptKey(encryptedFlag)));
} else {
console.log('Failed to retrieve or decrypt flag.');
}
} catch (error) {
console.error('Error during decryption process:', error);
}
})();
```
:::

:::success
```
Flag: MSEC{Fl49_Ch3ck3r_W1th_4Symm3tr1c_Cryp70}
```
:::
<div id="Truyện cười Remind 3"></div>
### 4. Truyện cười Remind 3
Chúng ta có thể kể đến một vài tool như dex2jar , jadx , JD , JAD , Procyon , CFR,... Bây giờ chúng ta sẽ cùng thử dịch ngược `truyencuoiremind3.apk` với tool jadx. Đầu tiên, ta phân tích file AndroidManifest. Đây là một phần rất quan trọng đối với một ứng dụng Android.
```json!
******************* Permissions *******************
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
******************* Main Activity *******************
android:theme="@style/LaunchTheme"
android:name="com.example.truyencuoiremind3.MainActivity"
android:exported="true"
android:taskAffinity=""
android:launchMode="singleTop"
android:configChanges="fontScale|layoutDirection|density|smallestScreenSize|screenSize|uiMode|screenLayout|orientation|keyboardHidden|keyboard|locale"
android:windowSoftInputMode="adjustResize"
android:hardwareAccelerated="true">
******************* Intent Filter *******************
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
```
<div id="Ready to run 3000m"></div>
### 5. Ready to run 3000m
Ta có một tệp CTF.dll và một tệp flag.txt bị mã hóa. Mở tệp với DIE.
Packer Detected!! Kiểu gì cũng xuất hiện làm rối hoặc ẩn giấu đoạn mã đây.

Vì không thể debug tệp DLL với dnSpy được nên ta sẽ viết đoạn mã gọi hàm main của tệp DLL.
:::info
```csharp
using System;
using System.Reflection;
using System.Text;
class Program
{
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
if (args.Length == 0)
{
Console.WriteLine("Vui lòng cung cấp tên tệp làm tham số.");
return;
}
string dllPath = @"";
try
{
Assembly assembly = Assembly.LoadFrom(dllPath);
Type programType = assembly.GetType("MTA.Program");
if (programType != null)
{
MethodInfo mainMethod = programType.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (mainMethod != null)
{
mainMethod.Invoke(null, new object[] { args });
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Lỗi khi tải hoặc xử lý DLL: {ex.Message}");
}
}
}
```
:::
Mở tệp CTF.dll trong dnSpy. Chương trình yêu cầu ta truyền tham số là một tệp để mã hóa.

Chương trình tạo `Key = SHA256("Secret Sauce")`, mã hóa tệp bằng thuật toán AES - CBC rồi lưu vào tệp mới IV (8 bytes) và cipher. Thuật toán có vẻ dễ, nhưng khi giải mã thì không ra kết quả. Đó là tính năng của chương trình.

Không phải ngẫu nhiên mà người ra đề lại in như thế này cả. Khi chuyển qua chế độ xem IL Code, ta sẽ thấy đoạn mã đã bị lấp đầy bằng opcode NOP. Có đoạn mã ẩn ở đây.

Ta quan sát PE Header, tại Cor20 Header có trường ManagedNativeHeader khác không, tức là có Native Code được thực thi. Mở tệp trong HxD vào tìm đến địa chỉ 0x1558.

Ta phát hiện từ RTR - Signature của ReadyToRun Format và kĩ thuật ẩn giấu đoạn mã là R2R stomping.
Ta sử dụng ILSpy thay thế cho dnSpy và để decompile ở chế độ R2R. Chương trình kiểm tra tên của tệp đã đọc có là flag.txt không. Nếu có thì tiến hành mã hóa tệp ấy.

Từ đây, ta viết đoạn mã giải mã toàn bộ thuật toán.
:::info
```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import binascii
cipher_hex = "93AC27813F28D91E049BE2172C0B6EA2A880E5D6296B43761F1D4932DAF956978087FE97C42E7C726C3A84F7258F128A"
key_hex = "d11cffafc91df75d25bbf5ed9c65e8f376aa0429b395398498784bce47336eeb"
iv_hex = "066C556114FB78340A9EFBC40A1929A5"
cipher = AES.new(binascii.unhexlify(key_hex), AES.MODE_CBC, binascii.unhexlify(iv_hex))
decrypted_bytes = unpad(cipher.decrypt(binascii.unhexlify(cipher_hex)), AES.block_size)
flag = bytearray((byte ^ 0xa0 ^ i) for i, byte in enumerate(decrypted_bytes))
print(flag)
```
:::
:::success
```
Flag: MSEC{Y0u_4re_re4dy_t0_get_100k_fr0m_Icefr0g2k}
```
:::