DLL injection

Trong lập trình máy tính, DLL injection (dịch: Tiêm chích DLL) là một kỹ thuật được sử dụng để chạy mã code trong của một 'tiến trình khác' thông qua cách ép nó tải một thư viện liên kết động. DLL injection hay được các 'chương trình bên ngoài' sử dụng để gây ảnh hưởng đến hành vi của một 'chương trình khác' theo cách mà tác giả của nó không hề tiên liệu hoặc dự định. Ví dụ, 'mã được tiêm' có thể (hook) vào các lời gọi hàm hệ thống, hoặc đọc nội dung của các hộp văn bản mật khẩu, những điều vậy không thể được thực hiện theo cách thông thường. Một chương trình mà được sử dụng để tiêm 'mã tùy ý' vào các 'tiến trình tùy ý' thì được gọi là một DLL injector.

Gian lận trong các trò chơi trực tuyến
DLL injection là một phương pháp gian lận phổ biến trong các trò chơi bởi việc chạy một DLL bên trong một chương trình cũng như việc có thể thực thi code vào trong game đều là những việc rất dễ dàng. Tuy nhiên, hầu hết các trò chơi đều tự bảo vệ chính mình khỏi DLL injection.

Lối tiếp cận trên Microsoft Windows
Trên Microsoft Windows, có nhiều cách để ép một tiến trình phải nạp và thực thi code của một DLL, nằm ngoài ý định của tác giả phần mềm đó:

Các DLL được liệt kê trong mục registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs đều được tải vào mọi tiến trình có tải trong lời gọi ban đầu tới DLL đó. Bắt đầu với Windows Vista, AppInit_DLL bị tắt theo mặc định. Bắt đầu với Windows 7, cơ sở hạ tầng của AppInit_DLL hỗ trợ . Bắt đầu với Windows 8, toàn bộ tính chức năng của AppInit_DLL đều bị tắt nếu có bật Secure Boot, bất chấp code signing hoặc thiết lập registry.
Các tệp DLL được liệt kê trong registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs đều được nạp vào mọi tiến trình có gọi các hàm Win32 API là CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, CreateProcessWithTokenW và WinExec. Đấy là cách đúng để sử dụng DLL injection một cách hợp lẽ trên phiên bản Windows hiện tại – Windows 10. DLL phải được ký bằng một chứng chỉ hợp lệ.
Các hàm để thao túng tiến trình như CreatRemoteThread hoặc các kỹ thuật code injection như AtomBombing, có thể được sử dụng để tiêm DLL vào một chương trình sau khi nó đã bắt đầu.
Mở một tới tiến trình mục tiêu. Điều này có thể được thực hiện bằng cách ra tiến trình đấy hoặc bằng cách lần nó dựa trên một đầu mối mà chắc chắn có tồn tại khi tiến trình đó chạy – ví dụ, một có tiêu đề đễ dự đoán, hoặc bằng cách lấy danh sách các 'tiến trình đang chạy' và quét tìm 'tên tệp' của 'tệp thực thi mục tiêu'.
Cấp phát bộ nhớ trong 'tiến trình mục tiêu', rồi ghi tên của 'DLL cần tiêm' vào bộ nhớ đó.
: Bước này có thể được bỏ qua nếu một 'tên DLL phù hợp' đã có sẵn trong tiến trình mục tiêu. Ví dụ: nếu một tiến trình mà có liên kết đến , , hoặc bất kỳ thư viện nào khác có tên kết thúc bằng ‘32.dll’, thì có thể tải thư viện có tên '32.dll'. Kỹ thuật này trong quá khứ đã được chứng minh là có hiệu quả để vượt qua hàng rào ngăn ngừa DLL injection của một tiến trình.
Tạo một thread mới trong 'tiến trình mục tiêu' với 'địa chỉ bắt đầu' của thread được đặt thành địa chỉ của LoadLibrary và đối số được đặt thành địa chỉ của chuỗi văn bản vừa được ghi vào mục tiêu ở bước 2.
: Thay vì ghi tên của một 'DLL cần tải' vào mục tiêu và bắt đầu thread mới tại LoadLibrary, người ta có thể viết hẳn một 'mã-để-thực-thi' vào mục tiêu và bắt đầu thread tại mã đó.
Hệ điều hành sau đó gọi vào thường trình sơ khởi ( routine) của 'DLL được tiêm'.
: Lưu ý rằng nếu không có sự đề phòng, lối tiếp cận này có thể bị 'tiến trình mục tiêu' phát hiện do các tin báo DLL_THREAD_ATTACH được gửi đến mọi 'mô-đun đang chạy' ngay khi một thread bắt đầu.
Các lời gọi của Windows như SetWindowsHookEx.
Sử dụng hàm SuspendThread hoặc NtSuspendThread để tạm đình chỉ tất cả các thread, sau đó sử dụng hàm SetThreadContext hoặc NtSetContextThread để sửa đổi ngữ cảnh của một thread đang tồn tại trong ứng dụng đó để thực thi 'mã được tiêm', từ đó có thể nạp DLL.
Khai thác các 'hạn chế về thiết kế' trong Windows và các ứng dụng có gọi hàm LoadLibrary hoặc LoadLibraryEx mà không chỉ định 'đường dẫn hợp cách đầy đủ' () đến DLL được tải.
ở mức hệ điều hành.
Thay thế một 'DLL đặc thù ứng dụng' bằng một 'bản thay thế giả mạo' có bản thực hiện của các hàm được xuất (export) tương tự như bản gốc.

Lối tiếp cận trên các hệ thống kiểu Unix
Trên các hệ điều hành kiểu Unix với 'trình liên kết động' dựa trên ld.so (trên BSD) và ld-linux.so (trên Linux), các 'thư viện tùy ý' có thể được liên kết với một 'tiến trình mới' bằng cách đặt tên đường dẫn của thư viện đấy vào biến môi trường , biến đó có thể được đặt trên toàn cục hoặc riêng lẻ cho một tiến trình.

Ví dụ, trong bash, lệnh này khởi chạy lệnh "prog" với 'thư viện dùng chung' từ tệp "test.so", tệp này được liên kết với nó tại thời điểm khởi chạy:
LD_PRELOAD="./test.so" prog
Một thư viện như vậy có thể được tạo ra bởi GCC bằng cách biên dịch 'tập tin nguồn' (chứa các toàn cục mới để được liên kết) bằng các tùy chọn -fpic hoặc -fPIC, và liên kết bằng tùy chọn -shared. Thư viện đấy có quyền truy cập vào các 'symbol ngoại tại' được khai báo trong chương trình như mọi thư viện khác.

Trên OS X, 'lệnh sau' khởi chạy lệnh "prog" với 'thư viện dùng chung' từ tệp "test.dylib" được liên kết với nó tại thời điểm khởi chạy:
DYLD_INSERT_LIBRARIES="./test.dylib" DYLD_FORCE_FLAT_NAMESPACE=1 prog
Cũng có thể sử dụng các kỹ thuật dựa trên debugger trên các hệ thống kiểu Unix.

Mã làm mẫu

Sử dụng hàm API LoadLibrary
Trong hệ điều hành Windows, có một cách đơn giản để tiêm DLL vào tiến trình khác là sử dụng hàm API CreateRemoteThread. Phương pháp là tạo một thread vào tiến trình đấy từ xa, thread đó sẽ nạp 'DLL mong muốn' vào. Hàm CreateRemoteThread yêu cầu một trỏ tới một hàm nằm ở trong 'không gian địa chỉ' của tiến trình mục tiêu – tức là "phải có sẵn" một hàm có chức năng nạp 'DLL mong muốn' ở ngay tiến trình mục tiêu. Có một sự thật có thể được khai thác, đó là kernel32.dll được ánh xạ tới cùng một địa chỉ trong hầu hết tất cả các tiến trình. Do đó hàm LoadLibrary của kernel32.dll (có chức năng nạp DLL bất kỳ) cũng sẽ có cùng địa chỉ trong hầu hết tiến trình. Cùng với việc hầu hết các ứng dụng đều liên kết với kernel32.dll nên hàm LoadLibrary luôn có sẵn ở hầu hết các tiến trình. Vì vậy có thể dùng CreateRemoteThread tạo một thread bắt đầu tại hàm LoadLibrary với đối số là đường dẫn tới DLL cần nạp. Xem ví dụ bên dưới cho cụ thể:
include
HANDLE inject_DLL(const char file_name, int PID) {
// lấy một handle tới tiến trình mục tiêu
HANDLE h_process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);

// lấy đường dẫn đầy đủ tới file dll
char fullDLLPath[_MAX_PATH];
GetFullPathName(file_name, _MAX_PATH, fullDLLPath, NULL);

// cấp phát bộ nhớ tại tiến trình mục tiêu
LPVOID DLLPath_addr = VirtualAllocEx(h_process, NULL, _MAX_PATH,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
// viết đường dẫn dll vào bộ nhớ đó
WriteProcessMemory(h_process, DLLPath_addr, fullDLLPath,
strlen(fullDLLPath), NULL);

// lấy địa chỉ của LoadLibraryA (đều giống nhau ở mọi tiến trình)
// để bật thread thực thi tại nó
LPVOID LoadLib_addr = GetProcAddress(GetModuleHandle("Kernel32"),
"LoadLibraryA");

// bắt đầu một thread thực thi từ xa tại LoadLibraryA
// rồi truyền đường dẫn dll làm đối số
HANDLE h_rThread = CreateRemoteThread(h_process, NULL, 0,
(LPTHREAD_START_ROUTINE)LoadLib_addr,
DLLPath_addr, 0, NULL);

// đợi cho đến khi thread đó hoàn thành
WaitForSingleObject(h_rThread, INFINITE);

// lấy 'giá trị trả về' của hàm LoadLibraryA nếu cần
DWORD exit_code;
GetExitCodeThread(h_rThread, &exit_code);

// giải phóng handle của thread vừa mới được inject...
CloseHandle(h_rThread);
// ...và bộ nhớ được cấp phát cho đường dẫn DLL đấy
VirtualFreeEx(h_process, DLLPath_addr, 0, MEM_RELEASE);
// ...và handle cho tiến trình mục tiêu đấy
CloseHandle(h_process);

return (HANDLE)exit_code;
}

Tham khảo