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:
- Call
OpenProcess
for the target process, to get a handle that has appropriate access privileges.
- Use
VirtualAllocEx
to allocate a buffer in the target process, with a set of memory page access flags.
- Use
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.
Pseudocode:
rights = PROCESS_VM_OPERATION |
PROCESS_VM_READ | PROCESS_VM_WRITE |
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD;
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);