[C] CreateRemoteThread To Load DLL With A Specific Process

AceInfinity

Emeritus, Contributor
Joined
Feb 21, 2012
Posts
1,728
Location
Canada
Here's a test I put together for calling to load a dll from a process of choice.

Simple Summary: Basically what this does is it calls to inject into the specified process to call a dll which when loaded calls to the Windows API, the GetCommandLine() function which is provided as a parameter to the popular MessageBox() function to be shown to the end user. I've learned a lot about specific flags that certain program's use on my system by testing a few different processes out.

The idea is relatively simple, first calling OpenProcess to get a handle to the specified process. We also get the address of the function LoadLibraryA (ANSI) from kernel32.dll. Next, I make sure that we have memory allocated inside the process' address space so that we can do stuff with this reserved memory. The WriteProcessMemory function is called to write to this newly allocated memory, with the location of the dll to be loaded. Lastly, calling CreateRemoteThread to create a new thread within that process which calls LoadLibraryA() with the address of the returned memory space from VirtualAllocEx, which we used previously to write the location of the dll to.

Our dll just calls the Windows MessageBox() function from the Windows API with GetCommandLine() as one of its parameters.

Result:
bdMcXJg.png


Simple DLL:
Code:
[NO-PARSE]#include <stdio.h>
#include <Windows.h>

BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
  switch (reason) {
  case DLL_PROCESS_ATTACH:
    MessageBox(NULL, GetCommandLine(), "Command Line", MB_OK);
    break;
  }
  return TRUE;
}[/NO-PARSE]

Main:
Code:
[NO-PARSE]#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

void display_usage(const char *arg0)
{
  const char *ptr_slash = strchr(arg0, '\\');
  const char *bin = ptr_slash == NULL ? arg0 : strrchr(arg0, '\\') + 1;
  printf(
    "Usage: %s [DLLFile] [ProcessID]\n"
    "Example: %s \"C:\\drivers\\mydll.dll\" 1427\n"
    , bin, bin);
}

int main(int argc, char const *argv[])
{
  // Check argument count
  if (argc < 3) {
    display_usage(argv[0]);
    return 0;
  }

  // Parse PID argument
  int process_id = strtol(argv[2], 0, 10);
  if (process_id == 0) {
    fprintf(stderr, "Error: the specified process ID was invalid.\n");
    return 1;
  }

  const char *dll_path = argv[1];

  // Retrieve process handle from PID
  HANDLE p_handle = OpenProcess(
                      PROCESS_ALL_ACCESS, // Requested access level
                      FALSE,              // Inherit handle
                      process_id          // Process ID of process to open
                    );
  if (p_handle == NULL) {
    fprintf(stderr, "Error: the specified process couldn't be found.\n");
    return 1;
  }

  // Get the address of the LoadLibrary function (ANSI/Non-Unicode alias)
  LPVOID addr = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
  if (addr == NULL) {
    fprintf(stderr, "Error: the LoadLibraryA function was not found inside kernel32.dll library.\n");
    return 1;
  }

  // Allocate a new memory region inside the process' address space
  LPVOID arg = (LPVOID)VirtualAllocEx(p_handle,                 // Process handle
                                      NULL,                     // Start address for allocation region
                                      strlen(dll_path),         // Size of region to allocate
                                      MEM_RESERVE | MEM_COMMIT, // Allocation type flags
                                      PAGE_READWRITE            // Memory protection
                                     );
  if (arg == NULL) {
    fprintf(stderr, "Error: the memory could not be allocated inside the chosen process.\n");
    return 1;
  }

  // Write the dll filepath to the memory space allocated from the call to VirtualAllocEx()
  // This is the argument for the LoadLibraryA() call
  int n = WriteProcessMemory(p_handle,         // Process handle
                             arg,              // Base address to write to
                             dll_path,         // dll_path
                             strlen(dll_path), // dll_path length
                             NULL              // Number of bytes written
                            );
  if (n == 0) {
    fprintf(stderr, "Error: there was no bytes written to the process's address space.\n");
    return 1;
  }

  // Inject the DLL into the specified process' address space
  DWORD thread_id = 0;
  HANDLE threadID = CreateRemoteThread(p_handle,                     // Process handle
                                       NULL,                         // Security descriptor
                                       0,                            // Initial stack size (bytes)
                                       (LPTHREAD_START_ROUTINE)addr, // Function address
                                       arg,                          // Function variable
                                       0,                            // Creation flags
                                       &thread_id                    // Thread identifier variable
                                      );
  if (threadID == NULL) {
    fprintf(stderr, "Error: the remote thread could not be created.\n");
    return 1;
  }

  printf(
    "DLL File: %s\n"
    "Process ID: %d\n"
    "Success: Remote thread was successfully created."
    "\n\tThread ID = %d\n"
    , dll_path, process_id, thread_id);

  // Close the handle to the process
  CloseHandle(p_handle);
}[/NO-PARSE]

Perhaps more argument handling could be added, but this was just a simple test anyways.
 
Last edited:
Few thoughts:
1. Try boot logging via WPT tools - you'll see some processes and their params, that are not visible in other way
2. Using MessageBox requires mapped user32.dll, so for console processes (and as a general rule) it's better to open file / write data / close file using kernel32 or ntdll functions.
3. Some processes are protected - since Windows 8.1 most of the system processes don't allow such activity. (What's Changed in Security Technologies in Windows 8.1). It's even possible to enable protection by 3rd party software vendors.
4. Yuo can also use the AppInit_DLLs registry entry to force the system to do the loading work for you (only for processes that load user32.dll). There are also other ways to inject the dll.
5. ASLR! :)
6. And finally: you could access the start params of another process without injecting the dll.

But I'm sure you wanted to test the injection in your system, so treat all these thoughts as thougths, nothing more. I think the next step for you is hooking :)

m.
 
I've already done inline hooking before. This is all notable to me, but the most visual way was to use a MessageBox for this demonstration, and most Windows programs will use functions most commonly exported from user32.dll anyways.

2. This won't really matter much, the console program is not what executes the function anyways it just writes to the process' memory to call to load my dll, and you're near guaranteed that the process injected is not a console process either, and even if it is, the LoadLibrary injected call will result in it being dependent on what my dll has available anyways, as it loads the necessary library to call the exported function. Linkage to user32.dll occurs through my dll which is loaded by the program through a call to LoadLibrary(). The original program doesn't have to have change anything. And by this same principle, this is how persistence from lots of malicious viruses work on program startup, without a registry entry or even being inside the Startup folder, making it virtually untraceable; there's no forensic footprint left behind. Btw, from my own dll I'm using dynamic linking to the function, so it's not really that big of a deal to begin with. My dll is entirely separate from the process, so it doesn't really matter in this case.

Plus, I didn't want to provide anything in too much detail for such techniques that could be used for malicious purposes. :thumbsup2:

This was the purpose of the thread:
CreateRemoteThread To Load DLL With A Specific Process

Not quite seeing how to get the command line arguments for each process. This is only a programming demonstration.

~Ace
 
Last edited:

Has Sysnative Forums helped you? Please consider donating to help us support the site!

Back
Top