The assumption does not hold true, as it is possible to alter page protection flags after you've allocated memory.
The usual mechanism for code injection on Windows is as follows:
OpenProcess for the target process, to get a handle that has appropriate access privileges.
VirtualAllocEx to allocate a buffer in the target process, with a set of memory page access flags.
WriteProcessMemory to copy the memory to the target.
- Either patch existing code to jump to the new code block, or use
CreateRemoteThread to execute within the process via a new thread.
Now, there are two options here. The first is that you can specify
PAGE_EXECUTE_READWRITE as a flag to
VirtualAllocEx, so that you have the right to use
WriteProcessMemory on that page, and also the right to execute that memory when you get to step 4. This is the "lazy" way that leads to having RWX buffers hanging around. The alternative way is to pass
PAGE_READWRITE when allocating the block, then write the code, and call
VirtualProtectEx to swap the flag over to
PAGE_EXECUTE_READ before step 4. This gives you an RW buffer when copying data, then an RX buffer when executing.
rights = PROCESS_VM_OPERATION |
PROCESS_VM_READ | PROCESS_VM_WRITE |
handle = OpenProcess(rights, false, pid);
targetAddr = VirtualAllocEx(handle, NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
buffer = "Hello, world!\0";
bytesWritten = 0;
WriteProcessMemory(handle, targetAddr, buffer, 14, &bytesWritten);
oldProtect = 0;
VirtualProtectEx(handle, targetAddr, 4096, PAGE_EXECUTE_READ, &oldProtect);
threadId = 0;
CreateRemoteThread(handle, NULL, 0, targetAddr, NULL, 0, &threadId);