Injecting and executing shellcode from a local or target process requires memory where the shellcode could be written to, read from and executed.
Some shellcode injection techniques allocate
PAGE_EXECUTE_READWRITE memory block, fill it with shellcode and create a thread pointing to that shellcode. It's not a very common thing for bening applications to do and this is something AV/EDR solutions may punish you for if they catch you doing it.
Some techniques allocate
PAGE_READWRITE first, write shellcode to the allocated memory, protect it with
PAGE_EXECUTE_READ and then execute it, which means that at no point in time there's an RWX memory block in the target process. It is a bit stealthier and may help one sneak past AVs/EDRs.
What both techniques have in common is that they still need to allocate and protect memory (RW -> RX or RWX). Having said that, it is possible to brute-force/enumerate currently running target processes on the compromised system - search through their allocated memory blocks and check if any those are protected with RWX, so we can attempt to write/read/execute them, which may help evade some optics.
In this lab I will write a simple program that will:
Loop through all the processes on the system
Query each process's memory information
Loop through all allocated memory blocks in each process
Check for any memory block that is protected with RWX && is private && is committed
If the above condition is met
Print out address of the memory block
Write shellcode to that memory block
Create a remote thread that points to the shellcode written in the above step
Running the Code with breakpoint set on line 31 will be hit if the conditions on line 27 are met. The conditions we are checking for are:
mbi.AllocationProtect == PAGE_EXECUTE_READWRITE&& mbi.State == MEM_COMMIT&& mbi.Type == MEM_PRIVATE
Once the breakpoint is hit, we can see that the memory region 27c727a0000 is RX protected, is private and commited and now contains our shellcode (starting with bytes fc 48 83 e4) :
If you noticed and were wondering...
When you call
WriteProcessMemoryand tell it to write to memory that is read-only, the
WriteProcessMemorysucceeds. How can that be?
WriteProcessMemorytries really hard to please you.
As I noted some time ago, the primary audience for functions like
WriteProcessMemoryis debuggers. And when debuggers try to patch memory, it’s often for things like patching in a breakpoint instruction or doing some edit-and-continue magic. So the
WriteProcessMemorytries really hard to get those bytes written. If the page is read-only,
WriteProcessMemorytemporarily changes the permission to read-write, updates the memory, and then restores the original permission.
“No need to thank me, just trying to help.”
There is a race condition if the target process happens to be manipulating the page protection at the same time that
WriteProcessMemoryis. But that’s okay, because the intended audience is debuggers, and debuggers will freeze the target process before trying to edit its memory.
There is no security hole here, because the way the
WriteProcessMemoryfunction changes the page protection is basically
VirtualProtectEx, so it will succeed only if you already could have modified the protections yourself anyway. If you didn’t have permission to change the protections, then
WriteProcessMemory‘s attempt to change the protections would fail too.
Let's build the program and run it - we can see we got some meterpreter shells.