How can I generate a stack backtrace that is independent of ASLR?


When you capture a stack backtrace with the Capture­Stack­Backtrace function, the addresses returned are absolute addresses. If you're capturing these values for future correlation, then saving the raw addresses is not interesting because there's no guarantee that the modules in your process will be loaded at the same address every time. And indeed, with address space layout randomization (ASLR), they will almost certainly not be loaded at the same address each time.

So how do you save this backtrace in a way that lets you recognize it if it happens again?

For each address in the stack backtrace, convert it to a module and an offset. You can use the Get­Module­Handle­Ex function to obtain the handle to the enclosing module. This is useful for two things:

  1. You can call Get­Module­File­Name to get the name of the module. You probably want to save only the file name portion and remove the directory, because the directory can vary from machine to machine.

  2. You can subtract the module handle from the raw pointer, resulting in an offset.

This combination of module and offset is independent of ASLR, in the sense that if ASLR loads the module at another address, the offset of the function in the backtrace will remain the same. And from the module and offset, you can reconstruct the original stack backtrace.

You can feed the module name and offset into a hash function if you want to generate a signatore for the stack trace.

Comments (10)
  1. Ray Koopa says:

    I often read the title of your articles and think “Damn, yeah, I wonder how ( / why) this is done, probably complicated stuffs…”, and at the end I’m like “Oh well, that was logical and really easy!” =)

    1. Antonio Rodríguez says:

      Just apply Occam’s razor. I’m on the other end: I assume things have the simplest (and most boring) explanation, and I’m usually right. Specially with Raymond’s articles.
      For example: keeping an API undocumented frees the OS developers from, well, documenting it and maintaining back compatibility, so what’s the need of a conspiracy theory to explain it being undocumented? Of course, “Microsoft hurts competence by using internal APIs” makes a great headline. But it doesn’t make much sense if you stop to think about it.

  2. Tautvydas says:

    Unfortunately, Get­Module­Handle­Ex is not allowed on Windows Store. Fortunately for our use case, we only care about functions from our own DLLs, so we link them with “/MAP” option, then after linking we parse the map file to get the list of function addresses in our modules and then at runtime subtract the __ImageBase (thanks to https://blogs.msdn.microsoft.com/oldnewthing/20041025-00/?p=37483) of our DLL from address returned by RtlCaptureStackBackTrace and do a binary search on the list of functions we got the from the map file to find which function/module it was. It’s not perfect, but works well enough for our purposes.

    1. Joshua says:

      Scan backwards from any allowed function in kernel32.dll to find kernel32.dll’s load address and call GetProcAddress() for the one you want.

  3. Myria says:

    Something implied above but not explicitly stated is that the delta between any symbol and the base address of a module is a constant. Modules are always contiguous blocks of memory, and when relocating them, the entire block is shifted.

    1. Voo says:

      Indeed – nothing else could work considering that constants inside of modules ate are often addressed via relative offsets to the instruction pointer.

      If you could separately relocate posts you’d have to fix up those offsets which might be impossible because the distance might be too large to be encoded in the same slave as before.

      1. Darran Rowe says:

        Did you write that on a phone or tablet? Some of the words are… interesting.

        1. ElectronShepherd says:

          Not everyone is a native English speaker – it might be that Voo is using machine translation to read the post (or, as is very common, their language knowledge is good enough to read and understand text, but not to write it), and is using machine translation from his/her native language to post a reply.

          1. Kemp says:

            It looks like typos/autocorrect issues to me. For example, are -> ate is an easy typo to do, but a very unlikely choice for a translation program. The overall quality of the text is also far higher than I’d expect from the typical translation programs. If we assume a swipe style mobile keyboard then “slave” is very likely to be “space”. Not sure what “posts” is, possibly “parts” except that o is quite far away from a.

  4. Killer{R} says:

    VirtualQuery/GetMappedFileName will do same but will not stuck if some other thread hanging inside loader lock waiting you..

Comments are closed.

Skip to main content