VirtualProtectEx to bypass ASLR : A specific case study
More than a year ago, I developed a local privilege escalation exploit for a product (that I cannot disclose unfortunately) in which I had to bypass ASLR.
For the record, these are the protections enabled in the targeted service’s binary, it is a 32-bit executable running under Wow64 on 64-bit systems.
Basically, I was able to communicate through IPC with the system service and tell it to execute a function in its address space by pointer (it’s a bit more tricky than this but you get the point). Actually, this would have been impossible if CFG was enabled.
Within the main module, I have located a function that invokes «system()» with an argument that I control. At this point, it was just a matter of bypassing ASLR in order to get that function’s address and elevate privileges on the machine. However, I couldn’t trigger any leaks through the IPC channel to get or deduce the main module’s base.
But as luck would have it, the service exposed some other functionality through IPC and one of them was the ability to call VirtualProtectEx and letting us supply a PID, the address, the size, and the new protection. The service was also kind enough to return the old protection of the region to our process via IPC.
Bypassing ASLR should be obvious by now knowing these two crucial points :
- The function that we want to invoke resides in the main module.
- On Windows the main executable’s module of a process is the first module, with the lowest address in the user address space.
It is now only a matter of writing code to communicate with the service and to brute-force the location of the main module; we do that by looking for the first executable page in the address space and then calculating the main module’s base : generally by subtracting 0x1000 from that page since the .text section is the first section.
The pseudo-code below shows an example of how this was done :
|pid = /*Locate the service’s pid with the help of NtQuerySystemInformation*/;|
|for( Page = 0x1000; Page < 0x7fffffff; Page += 0x1000 )|
|Page_cp = Page;|
|OldProtection = CommunicateServiceVirtualProtect(|
|pid, //We give the service its own pid|
|Page, //Address of the page in the service’s process|
|PAGE_EXECUTE_READWRITE, //Change to the most permissible protection to avoid crashes|
|if ( OldProtection == -1 ) //Invalid page|
|if ( OldProtection == PAGE_EXECUTE_READ )|
|//this is the main module’s .text section|
|Base = Page — 0x1000;|
|Page = 0x7fffefff; //to break after restoring the old protection|
|//restore the old protection|
|//Use the base to calculate the function address|
|pFunc = Base + FuncOffset;|
Launching a new process with SYSTEM privileges was easy at this point.
Thank you for reading and until another time 🙂
You can follow me on Twitter : here