Search This Blog

Friday, July 01, 2005

The Art of Imperialism - Windows NT

While not by any means a common thing to do, occasionally it is necessary to inject code or data into another process, without the knowledge of that process. On Windows this is fairly easy to do (this is one of those fiercely platform dependant features, and I only known how to do it on Windows). In fact, there are several API functions that exist for this purpose; however, different methods are required for the Windows NT and Windows 9x kernels.

Remember that in 32-bit Windows, each process has an isolated memory space. A pointer to something in your process will not work to access memory in another process, and vice-versa. For this reason, special methods must be used to allocate and access memory in other processes.

For Windows NT, this process is very straightforward, and uses the VirtualAllocEx and WriteProcessMemory functions. VirtualAllocEx is just like VirtualAlloc - it allocates a region of virtual address space - except that VirtualAlloc allocates memory in the current process, while VirtualAllocEx takes a handle to the process to allocate in. WriteProcessMemory writes a buffer to memory in the process whose handle you supply it.

However, before you can use either of these functions, you have to obtain a handle for the process you want to access, using OpenProcess. This is, really, the hard part, because it's where permissions checking gets done; you must have permission to access the process. When calling OpenProcess, you must supply the process ID of the process, and the access to the process that is desired. As stated in MSDN, to use VirtualAllocEx and WriteProcessMemory, only the PROCESS_VM_OPERATION and PROCESS_VM_WRITE permissions are necessary (PROCESS_VM_READ as well, if you want to also read the process' memory). With Windows NT's security model, it's best to request as little access as possible to get the job done; the more access you request, the more likely it is that you'll be denied.

When calling VirtualAllocEx, you must specify the allocation type and memory protection to apply to the allocated memory. Since you want actual memory, and not simply to reserve a portion of the address space for future you, you want to use MEM_COMMIT as the allocation type. The memory protection depends on what you want to do with it. Obviously you'll need it to be both readable and writable. However, if you're going to write executable code to the memory, you'll also need the memory to be executable, which is specified in the memory protection, as well. Thus, use PAGE_EXECUTE_READWRITE for executable code memory, and PAGE_READWRITE for memory which will not be executed.

// Allocates memory in the specified process, and writes the string "Squish!" to it.
bool InjectSquishNT(DWORD nProcessID)
{
const char *pszSquish = "Squish!"; // The string to write
const SIZE_T nSquishSize = strlen(pszSquish) + sizeof('\0'); // Length of the string, including the terminating null

// Open the process
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, nProcessID);
if (!hProcess)
return false; // Failed

bool bSuccess = false; // Failed until proven otherwise

// Allocate the memory
void *lpMemory = VirtualAllocEx(hProcess, NULL, nSquishSize, MEM_COMMIT, PAGE_READWRITE);
if (lpMemory)
{
// Write the string
SIZE_T nBytesWritten;

if (WriteProcessMemory(hProcess, lpMemory, pszSquish, nSquishSize, &nBytesWritten) && nBytesWritten == nSquishSize)
bSuccess = true;
else
VirtualFreeEx(hProcess, lpMemory, 0, MEM_RELEASE); // If the write failed, free the allocated memory. If the write succeeded, leave it.
}

// Close the process handle
CloseHandle(hProcess);

return bSuccess;
}

No comments: