Don’t always trust the disassembler…

A coworker of mine just ran into a particularly strange bug in the WinDbg disassembler that turns out to be kind of nasty if you don’t watch out for it.

There is a bug with how WinDbg disassembles e9/imm32 jump instructions on x86. (The e9/imm32 instruction takes a 4-byte relative displacement from eip+5, which when combined with eip, is the new eip value after the jump. It is thus in the format e9XXXXXXXX, where XXXXXXXX is the relative displacement.)

Specifically, if the absolute value of the relative displacement immediate value for the e9 opcode is the virtual address of a named symbol in the target, WinDbg incorrectly displays the target of the jump as that symbol. This does not occur if it does not resolve to a named symbol, i.e. something that would just show up as a raw address and not as a name or name+offset in the debugger.

For example, here’s a quick repro that allocates some space in the target and builds an e9/imm32 instruction that is disassembled incorrectly:

0:001> .dvalloc 1000
Allocated 1000 bytes starting at 00980000
0:001> eb 00980000 e9;
ed 00980001 kernel32!CreateFileA;
u 00980000 l1
00980000 e9241a807c      jmp kernel32!CreateFileA (7c801a24)

Here, CreateFileA is not the target of the jump. Instead, 7d181a29 is the target.

You are most likely to see this problem when looking at e9-style jumps that are used with function hooks.

It is interesting to note that the uf (unassemble function with code analysis) is not fooled by the jump, and does properly recognize the real target for the jump. (Here, the target is bogus, so uf refuses to unassemble it. However, in testing, it has followed the jump correctly when the target is valid.)

This bug is appearing for me in WinDbg 6.6.7.5. I would assume that it is probably present in earlier versions.

Here’s why you do not normally see this bug:

  1. This bug only occurs if the relative displacement, when incorrectly taken as a raw absolute virtual address, matches the absolute virtual address of a named symbol. This means that if you tried to resolve an address as a symbol, you would get a name (or name+offset) back and not just a number.
  2. Most relative displacements are very small, except in atypical cases like function hooks. This means that as a general rule of thumb, it is very rare to see a relative displacement for an e9/imm32 jump that is more than a couple of megabytes.
  3. There are rarely any named symbols in the first few megabytes of the address space. The first 64K are reserved (in non-ntvdm processes) entirely, and after that, things like RTL_USER_PROCESS_PARAMETERS and the environmental variable block for the process, and soforth are typically present. These are allocated at the low end of the address space because by default, the memory manager allocates from low addresses and up. Therefore, low address ranges are very quickly used up by allocations.
  4. Most images do not have a preferred image base in the extreme low end of the address space.
  5. By the time most DLLs with conflicting base addresses are being loaded, the low end of the address space has been sufficiently consumed and/or fragmented such that there is not room to map the image section view in the extreme low end of the address space, continuing to prevent there being named symbols in this region.

Given this, it’s easy to see how this bug has gone unseen and unfixed for so long. Hopefully, it’ll be fixed in the next DTW drop.

Comments are closed.