We all know and love relocations. They are a fundamental part of the Windows PE loader, allowing the loader to adjust the addresses of code and data sections in memory to match the actual memory layout. Yesterday however I got an idea while showering: What if we could control the output of the relocations? Normally, this shouldn’t be possible as we do not know the target base address of the module. But what if we could? If we could predict the base address of the module, we could precalculate data that would be unscrambled into valid code by the Windows PE loader.
So I started experimenting…
The first obvious thought was what happens if we build an executable with relocations and disable the “DLL can move” flag inside the optional header. As expected, the loader will not move the module and load it as its preferred base address, but also skip all relocations. Same behavior can be observed if we add IMAGE_FILE_RELOCS_STRIPPED to the file header Characteristics. So far so good, thats what I (and I guess everyone else) expected.
My next idea went a bit deeper. We all know that windows uses 48 bit addressing in usermode, which means that the upper 16 bits are reserved and not allowed to be used inside a usermode module base address. So I tried building an executable with the base address set to 0x2000000000000000. To my surprise, it loaded just fine and got relocated to a normal base address, so the behaviour was no different than normal. Now heres the catch: If we set a reserved base address like this, AND remove the “DLL can move” flag from the pe header in combination, the loader will try to load it at the reserved base address, realise that it can’t, and fallback to the base address 0x10000 which is always the same. And even better: It processes our relocations. As this behaviour is always the same on every tested version of Windows 10, Windows Server and Windows 11 we can now control the target value of our relocations.
Bingo!
Afterwards i started working on a proof of concept that would abuse this behaviour to generate executable files that would appear to have nothing but trash inside them without any valid code, that would get fully fixed up and executed by the loader through relocation processing. This was quite easy, I would just build a seperate shellcode executable and scramble its bytes by the delta value between the fucked up base address and the windows fallback base address, then output a new file with the scrambled bytes and a bunch of IMAGE_REL_BASED_DIR64 relocations that would unscramble the bytes on load and start executing them.
Another interesting fact is, that this completely breaks static analysis tools like IDA or Binary Ninja, as unlike the windows pe loader they will just ignore the high base address and won’t fallback to 0x10000, resulting in even more scrambled output.
The shellcode itself is just a freestanding position independent function that shows a message box. I published the entire proof of concept on github here. It has it’s limitations, so if you want to seriously use this on full binaries, preparing it for that is the exercise for the reader.
To finish this off, you can download a sample binary here and enjoy this video of it in action: