Search This Blog

Friday, July 01, 2005

The Art of Imperialism - Windows 9x

So, we have a really nice method of injecting code or data into a foreign process in Windows NT. The only problem is that Windows 9x doesn't HAVE VirtualAllocEx; thus, different methods are required. In fact, Windows 9x has no way of specifically allocating memory in a foreign process; however, a trick of the trade can be used to effectively do just that.

File mapping is a technique on various operating systems that allows a file on disk to appear to the program as if it were a region of memory, accessed by a pointer. On Windows NT, file mappings act very much like regular memory - that is, only those processes that open a file mapping will have it in their address space. Windows 9x, however, works more peculiarly. In Windows 9x, the upper 2 gigabyte of each process' virtual address space are shared among all processes. The kernel stores its data there (Windows NT also does this, although just about everything in the upper gigabytes is accessible only from kernel mode); however, in Windows 9x, all file mappings are in the shared memory space, not only available to all processes, whether they mapped the file themselves, but also at the same address for every process. When one process opens a file mapping, all other processes instantly have access to that mapping.

However, in addition to mapping a disk file into a process' address space, it is possible to create a file mapping that does not use a file. In this case, the file that gets mapped is the system's swap file. That is, you're using virtual memory. As creating a file with our data would be significantly more work, we will use the swap file to store our data.

Mapping a file involves two steps in Windows: the creation of the kernel file mapping object (which you receive as a handle) with CreateFileMapping, and the mapping of that mapping object into the process' address space with MapViewOfFile.

In CreateFileMapping, you must specify the file to map, the security attributes for the file mapping, and the protection for the file map (read, write, etc.). Since we're using Windows 9x, which ignores most of the security attribute stuff, we can just specify NULL for this, which gives the mapping the default security attributes. And since we're using the swap file, we specify INVALID_HANDLE_VALUE for the file handle. As with before, we'll want both read and write access, so the protection will be PAGE_READWRITE (file mappings don't support execution, so we don't specify it; this is okay, because Windows 9x doesn't support execution protection at all, so we'll still be able to execute code in the file mapping).

When calling MapViewOfFile, you must again specify the protection for the memory that is mapped. This will simply be FILE_MAP_WRITE, which specifies both read and write access. Again, execution access isn't needed, because Windows 9x doesn't support that option anyway (memory is always executable).

However, there's a snag in this process. The file mapping is created and mapped in the current process. While mapping the file in the current process maps it in all processes in Windows 9x, this mapping will be closed when the owning process (yours) exits. I'm currently investigating a possible solution to this problem, but getting info about Windows 9x is pretty difficult these days, given how Microsoft no longer supports 9x. For now, you'll have to keep your process alive until the foreign process no longer needs the mapping you created.

// Makes the string "Squish!" available to all processes
bool InjectSquish9x()
const char *pszSquish = "Squish!"; // The string to write
const SIZE_T nSquishSize = strlen(pszSquish) + sizeof('\0'); // Length of the string, including the terminating null

// Create the file mapping kernel object
HANDLE hMapping = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, nSquishSize, NULL);
if (!hMapping)
return false;

// Map the view of the file
void *lpMemory = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, nSquishSize);
if (lpMemory)
memcpy(lpMemory, pszSquish, nSquishSize); // Write the string

return true;
// If we succeeded, leave the mapping open, so that the memory will remain available to the foreign process. If we didn't succeed, close the mapping.

return false;

No comments: