What is a DLL?
DLL export các hàm để có thể được sử dụng bởi một tiến trình. File DLL không thể được thực thi giống file EXE. Thay vào đó, nó cần phải được gọi bởi một tiến trình để thực thi. Ví dụ, file kernel32.dll
export hàm CreateFileW
nên nếu tiến trình nào sử dụng hàm này thì nó cần phải nạp file kernel32.dll
vào bộ nhớ của nó.
Một vài DLL được nạp vào bộ nhớ tự động do các hàm mà các DLL đó export cần thiết cho việc thực thi của tiến trình một cách bình thường. Ví dụ: ntdll.dll
, kernel32.dll
và kernelbase.dll
.
Hình dưới minh họa danh sách các DLL mà chương trình explorer.exe
sử dụng:
System-Wide DLL Base Address
Hệ điều hành Windows sử dụng cùng một địa chỉ vùng nhớ cho một vài DLL hệ thống và có thể được sử dụng bởi tất cả các tiến trình có trên máy. Điều này là để tối ưu hóa bộ nhớ và cải thiện hiệu năng. Hình bên dưới minh họa cho việc kernel32.dll
được nạp lên tại cùng một địa chỉ vùng nhớ (0x7fff9fad0000
) ở nhiều tiến trình khác nhau.
DLL Entry Point
Các DLL có thể chỉ định một hàm đầu vào (entry point function) khi một sự kiện nào đó xảy ra:
DLL_PROCESS_ATTACHED
- một tiến trình đang nạp DLL.DLL_THREAD_ATTACHED
- một tiến trình đang tạo một thread.DLL_THREAD_DETACH
- một thread kết thúc một cách bình thường.DLL_PROCESS_DETACH
- một tiến trình unload DLL.
Đoạn code sau minh họa cho cấu trúc code của một DLL:
BOOL APIENTRY DllMain(
HANDLE hModule, // Handle to DLL module
DWORD ul_reason_for_call, // Reason for calling function
LPVOID lpReserved // Reserved
) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACHED: // A process is loading the DLL.
// Do something here
break;
case DLL_THREAD_ATTACHED: // A process is creating a new thread.
// Do something here
break;
case DLL_THREAD_DETACH: // A thread exits normally.
// Do something here
break;
case DLL_PROCESS_DETACH: // A process unloads the DLL.
// Do something here
break;
}
return TRUE;
}
Exporting a Function
Để export một function, chúng ta cần sử dụng từ khóa extern
và __declspec(dllexport)
. Ví dụ:
////// sampleDLL.dll //////
extern __declspec(dllexport) void HelloWorld(){
// Function code here
}
Dynamic Linking
Việc nạp và liên kết code (các DLL) trong quá trình thực thi sử dụng linker và import address table (IAT) thay vì liên kết trong quá trình biên dịch được gọi là liên kết động (dynamic linking).
Việc gọi một hàm chẳng hạn như MessageBoxA
sẽ khiến cho hệ điều hành nạp DLL mà có export hàm này vào vùng nhớ của tiến trình gọi hàm. Cụ thể hơn, user32.dll
sẽ được nạp và quá trình này được thực hiện tự động bởi hệ điều hành khi tiến trình bắt đầu chạy.
Giả sử ta có file sampleDLL.dll
export hàm sau:
////// sampleDLL.dll //////
extern __declspec(dllexport) void HelloWorld(){
// Function code here
}
Loading a DLL
Trong trường hợp sampleDLL.dll
chưa có ở trong vùng nhớ của tiến trình, ta có thể nạp vào bằng hàm LoadLibrary
như sau:
HMODULE hModule = LoadLibraryA("sampleDLL.dll"); // hModule now contain sampleDLL.dll's handle
Có thể thấy, giá trị trả về của LoadLibraryA
là một HMODULE
.
Retrieving a DLL’s Handle
Trong trường hợp sampleDLL.dll
đã được nạp vào vùng nhớ thì có thể dùng hàm GetModuleHandle
để lấy ra handle của DLL như sau:
HMODULE hModule = GetModuleHandleA("sampleDLL.dll");
Retrieving a Function’s Address
Để sử dụng hàm khi DLL đã ở trong vùng nhớ của tiến trình thì cần lấy ra con trỏ chứa địa chỉ của hàm bằng GetProcAddress
:
PVOID pHelloWorld = GetProcAddress(hModule, "HelloWorld");
Invoking The Function
Cuối cùng, ta cần ép kiểu cho con trỏ chứa địa chỉ hàm về kiểu con trỏ hàm (Function Pointer) của hàm HelloWorld
để có thể gọi hàm:
// Constructing a new data type that represents HelloWorld's function pointer
typedef void (WINAPI* HelloWorldFunctionPointer)();
void call(){
HMODULE hModule = LoadLibraryA("sampleDLL.dll");
PVOID pHelloWorld = GetProcAddress(hModule, "HelloWorld");
// Type-casting the 'pHelloWorld' variable to be of type 'HelloWorldFunctionPointer'
HelloWorldFunctionPointer HelloWorld = (HelloWorldFunctionPointer)pHelloWorld;
HelloWorld(); // Calling the 'HelloWorld' function via its function pointer
}
Một ví dụ khác:
typedef int (WINAPI* MessageBoxAFunctionPointer)( // Constructing a new data type, that will represent MessageBoxA's function pointer
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
void call(){
// Retrieving MessageBox's address, and saving it to 'pMessageBoxA' (MessageBoxA's function pointer)
MessageBoxAFunctionPointer pMessageBoxA = (MessageBoxAFunctionPointer)GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
if (pMessageBoxA != NULL){
// Calling MessageBox via its function pointer if not null
pMessageBoxA(NULL, "MessageBox's Text", "MessageBox's Caption", MB_OK);
}
}
Ví dụ trên giả sử user32.dll
mà export hàm MessageBoxA
chưa được nạp vào vùng nhớ của tiến trình.
rundll32.exe
Chúng ta cũng có thể chạy những hàm được export bởi các DLL mà không cần lập trình. Cụ thể, ta có thể dùng rundll32.exe
để gọi hàm của DLL như sau:
rundll32.exe <dllname>, <function exported to run>
Ví dụ, để gọi hàm LockWorkStation
của User32.dll
để khóa màn hình, ta có thể dùng lệnh sau:
rundll32.exe user32.dll,LockWorkStation
Creating a DLL File With Visual Studio
Khi tạo một project có template là DLL, chúng ta sẽ được cung cấp các file là: framework.h
, pch.h
and pch.cpp
. Các file này là các precompiled header, được sử dụng để tối ưu việc biên dịch cho các project lớn. Đối với các project nhỏ, chúng ta có thể xóa chúng đi.
Sau đó, ta cần thiết lập project sao cho nó không sử dụng các precompiled header. Để thiết lập, chọn Properties > C/C++ > Precompiled Headers và thay đổi ‘Precompiled Header’ thành ‘Not Using Precompiled Headers’ rồi nhấn ‘Apply’.
Note
Toàn bộ code trong khóa học lập trình mã độc đều được viết bằng C nên ta cần phải đổi đuôi mã nguồn từ
.cpp
thành.c
.
Related
list
from outgoing([[MalDev - Dynamic Linking Library (DLL)]])
sort file.ctime asc