How important is it nowadays to ensure that all my DLLs have non-conflicting base addresses?


Back in the day, one of the things you were exhorted to do was rebase your DLLs so that they all had nonoverlapping address ranges, thereby avoiding the cost of runtime relocation. Is this still important nowadays?

This situation is another demonstration of how it is important for good advice to come with a rationale so you can tell when it becomes bad advice.

The rationale for rebasing goes like this: If a DLL is loaded at its preferred base address, then the image can be paged in directly from backing store without requiring any fixups. This means that the pages can be shared between processes, since each process gets an identical copy. (Of course, the sharing stops once somebody writes to the page and makes their copy different from the shared copy.)

If a DLL cannot be loaded at its preferred address, then the image will be relocated, and the entire relocated DLL is now backed by the page file.¹ This is a relative expensive operation, since the DLL has to be read from disk and fixed up, and a commit charge to the page file is incurred in order to ensure that there is space to write the fixed-up pages. Furthermore, if two processes relocate the DLL and happen by some coincidence to relocate them to the same place, Windows NT does not attempt to share the relocated images. There will be multiple copies in the page file.

The cost of this dynamic relocation is what rebasing attempts to avoid. Let's call this the "relocation penalty."

Enter ASLR.

ASLR causes the DLLs to be loaded at pseudo-random addresses. Consequently, a DLL will load its its preferred base address only in the case of an astonishing coincidence.

Okay, so let's go back to the rationale to see if it still applies.

Does a DLL being loaded away from its preferred base address incur a relocation penalty? If you think about it, ASLR means that no DLL ever loads at its preferred address, but we also saw that the kernel makes accommodates for this so that DLLs subjected to ASLR can still share pages, and it does so without forcing the entire DLL to be relocated on initial load. So there is no relocation penalty in the case where the DLL was relocated by ASLR.

But what if the DLL is relocated for some other reason? For example, it could be that the ASLR-chosen base address is not available in the process, because the process already allocated something else at that location. In that case, a traditional relocation must take place, and you pay the relocation penalty.

Ah, but here's the thing: When a DLL is loaded, ASLR will assign a base address randomly from among the available base addresses that are not already being used.² So you're not going to get into the "the ASLR-chosen base address is not available" scenario because ASLR chooses the DLL's base address from among the base address that are available.³

Okay, so you can still get into a conflict situation, but you have to really work at it. For example, you could load a DLL into one process, and get an ASLR-assigned base address. You then start a second process, intentionally allocate memory at that address (to force the collision), and then load the DLL. In this case, there will be a relocation because you squatted on the place where ASLR wanted to put the DLL. But this is no worse than what you had before ASLR: In the pre-ASLR world, squatting on a DLL's preferred base address would have forced a relocation penalty anyway.

So, let's see what the story is. To rebase or not to rebase?

In the presence of ASLR, rebasing your DLLs has no effect because ASLR is going to ignore your base address anyway and relocate the DLL into a location of its pseudo-random choosing.

Mind you, even though rebasing has no effect, it doesn't hurt either.

If you are on a system without ASLR (either because it predates ASLR, or because ASLR has been disabled for whatever reason), then rebasing will help, for the traditional reasons.

Mind you, systems without ASLR are really hard to find nowadays, so rebasing provides no benefit in the overwhelming majority of cases. But in that vanishingly small percentage of cases where you don't have ASLR, then rebasing helps.

Conclusion: It doesn't hurt to rebase, just in case, but understand that the payoff will be extremely rare. Build your DLL with /DYNAMICBASE enabled (and with /HIGHENTROPYVA for good measure) and let ASLR do the work of ensuring that no base address collision occurs. That will cover pretty much all of the real-world scenarios. If you happen to fall into one of the very rare cases where ASLR is not available, then your program will still work. It just may run a little slower due to the relocation penalty.

¹ More precisely, all the pages that contained fixups are put into the page file. We discussed this finer point last time.

² Okay, there's a third case, which is where ASLR has simply run out of base addresses. But again, this is no worse than what you had before ASLR: If you run out of base addresses, then it's every man for himself. Each time a new DLL loads, the kernel has to scrounge around for a large-enough chunk of available address space into which to load the DLL.

³ As a result, ASLR actually does a better job of avoiding collisions than manual rebasing, since ASLR can view the system as a whole, whereas manual rebasing requires you to know all the DLLs that are loaded into your process, and coordinating base addresses across multiple vendors is generally not possible.

Comments (27)
  1. kantos says:

    I am curious if this is still as much an issue in x64 where you have true position independent code segments. I suspect there would still have to be fixups because of strings and whatnot but I would imagine this would be less of an issue.

  2. Zan Lynx' says:

    I don't remember seeing an option for it in the Microsoft tools, but I could have missed it...

    Does Windows not have an option to build DLLs as Position Independent Code?

    In Linux we can build a shared object library with GCC's -fPIC option and then it doesn't care where it loads. All the machine instructions use relative offsets. Executables can be built with -fPIE and then they can be loaded at random memory locations too.

    PIC and PIE only became good options once AMD added a lot of instruction pointer relative addressing modes to AMD64, but it HAS been what, 15 years now?

    1. kantos says:

      As far as I'm aware all 64bit code on windows is PIC. Given the timing of the x64 compiler it makes sense as it was around the time of ALSRs introduction.

      1. Darran Rowe says:

        For Windows, x64 predates Vista by at least a year. There was a release of Windows XP and Windows Server 2003 for x64.

      2. Darran Rowe says:

        I would suggest you clarify your terminology a bit, because using relative jumps and global tables is just as position independent as using base addresses, relocations and fixups.

      3. poizan42 says:

        Also PIC have a performance penalty on x86 since there is no IP-relative addressing. You need a call/pop pair to get your current address. So either you litter your code with expensive calls (you don't), or you cache you base address in a register (but then you have fewer registers available and need to spill more often)

        When AMD designed x64 they realised the importance of position-independent code, so they added a IP-relative addressing scheme, which means it doesn't have the same performance penalty as on x86.

    2. Darran Rowe says:

      Well, if you use a general classification, Windows PE is position independent because they can load in anywhere. This article is on how ASLR affects things.
      But if you are wondering why Windows uses based images compared to the Linux style position independent code, the answer is mostly preference and history.
      There are some advantages to the way Windows does it over the way that Linux does it, like there is advantages to the way that Linux does it over how Windows does it. In the end, neither is clearly superior.

      1. I'd love to see a write-up of the advantages/disadvantages for both strategies from an academic standpoint. Using pointer-relative addressing seems a lot cleaner to me than a dynamic base address, but frankly the only assembly I've ever played around with is MIPS, which is very different from x86-64. I'd love to learn more about this.

      2. poizan42 says:

        As I pointed out in my other comment, PIC is expensive on x86 which may be part of the reason. Linux has just chosen to prefer security over performance. Also ELF files fully well can contain relocations, but code moved to being position-independent when the push for ASLR started.

        1. Darran Rowe says:

          "Linux has just chosen to prefer security over performance." I'm not sure of this statement, especially since Microsoft went on to use relocatable libraries with ASLR even on x64. Don't forget, the main reason why ASLR took longer to get into Windows was due to Microsoft being in the middle of developing Vista and not wanting to touch the kernel mode components of XP/2003.
          While it could very well have been that relocatable was chosen over IP relative addressing because of how expensive it was originally, that certainly isn't true with x64, yet they stuck with relocatable.

    3. Joshua says:

      I've been compiling this one x86 DLL full PIC for ages (it has to interact with managed code and is loaded late so has no idea what its base address will be). Windows supports this just fine. I wonder how the ASLR thing handles it though. The old toolchain required to build it doesn't know what /DYNAMICBASE is.

      1. Kirby FC says:

        If executables and DLLs are capable of being position independent, then why is ASLR even needed? Is it because most people use based images even though they don't need to?

        1. Darran Rowe says:

          No, this is a security measure. It prevents simple address based attacks.

        2. voo says:

          ASLR only works because dlls can be relocated. The idea here is that contrary to old times an attacker cannot know at which memory address a function will be, so it makes exploiting the program harder.

        3. Ken Hagan says:

          I imagine it is because the executable format has a field for base address and we need a flag to say "actually, pay no attention to that address". As others have noted, PIC is expensive to emulate on x86, so designing a preferred base address into the executable format was sensible enough back in 1990.

          To me, that cost assessment makes it all the more the surprising that it took so long for someone to add EIP-relative operations to Intel's instruction set. However, perhaps I'm reading it wrongly and it is the existence of that "workaround" that meant the benefits of PIC were never quite enough to justify the effort.

      2. Honest question: How do you generate vtables and other statically-initialized pointers in position-independent code?

        1. Joshua says:

          I checked. My compiler doesn't implement it. If I ask it to build PIC and there would be a vtable it says no.

          I've known how to do it for ages, but it's never been profitable for me to attempt. What would need to be done is to fill out the vtables in DllMain, preferably with compiler assistance.

        2. kantos says:

          It appears from a cursory google search that GCC uses a relocation table for vTables in PIC code as well as requiring that the vTables are initialized at load time. But it may use IP Relative for static calls.

          1. What did you use to search on Google? I tried "gcc pic vtable" and "how does gcc generate vtables for pic" but they didn't turn up any answers.

          2. Joshua says:

            @Raymond Chen: A search for `g++ "-fpic" vtable` reveals claim after claim that it works, but a pass through g++ -S reveals it does not. The compiler ignores -fpic -fPIC and generates a reloc vtable anyway.

          3. kantos says:

            It was "GCC PIC C++" which didn't get me to the answer directly, I had to go through the SO question that was the second result ( http://stackoverflow.com/a/967055/332733 ) to get to this document https://www.akkadia.org/drepper/dsohowto.pdf by Ulrich Drepper, and then skim through until page 29 where it describes what's done in the virtual table case. Other parts of the document cover non-virtual calls.

          4. Okay, so now I don't feel so bad that I couldn't find it with a cursory search. It was a second-order link.

  3. Stephen says:

    I see no reason why EXEs can't be moved, but they never seem to be. I don't think they have relocation info in general. If I built an EXE that did would it be shuffled with the Falls?

    1. Darran Rowe says:

      When you build an executable with /DYNAMICBASE, it enables relocation.
      For example, do a dumpbin of notepad.exe, you will notice things like no "Relocations Stripped" flag in the characteristics and there is a .reloc section.

  4. Paul says:

    So what if I'm using a non-MS dev environment, and don't have /DYNAMICBASE available. Does that imply ASLR never happens on my DLLs?

    1. Darran Rowe says:

      Not really because of the most obvious reason, the toolchain's linker may just set the flag on generated binaries.
      However, if you check the binary header's DLL characteristics and there is no dynamic base flag there, then it will not take part in ASLR. Even though there shouldn't be any difference between a binary with relocations in it and a binary compiled for ASLR, you need to specify the flag for it to happen.

      1. Unless you tell Windows to force it to happen!

Comments are closed.

Skip to main content