Bypassing Export address table Address Filter (EAF)

(An unfinished version of this blog post was accidentally published last week. In case you got a hold of a copy: I’ve made only small modifications, so no need to re-read the entire post. However, I did not released the source of my PoC shellcode earlier, so you may want to check if out).

In early September this year Microsoft released their Enhanced Mitigation Experience Toolkit v2.0 (EMET), which includes a new “pseudo”-mitigation called Export address table Address Filter (EAF). I decided to have a look at how this mitigation attempts to prevent exploits from succeeding and how an attacker might bypass it. For people that suffer from tl;dr syndrome, I’ve put my conclusion up front:

It is my conclusion that EAF should be effective at preventing most current shellcode from executing and therefore a useful mitigation. However, it is relatively simple to bypass. Proof of concept code to do this can be found here. I expect that if EAF becomes a common mitigation, attackers will update their shellcodes to bypass it. I cannot think of any effective way in which EAF can be updated that would not be relatively simple to bypass as well.

EAF works by setting a hardware breakpoint on the export address tables of the ntdll.dll and kernel32.dll modules in a process. When the breakpoint is triggered, EAF determines if the code that is trying to access the export address table is valid code for that process or malicious code injected into the process through an exploit. Most exploits will at some point inject and run shellcode into the target process. One of the first thing most shellcodes do is determine where certain functions are loaded in memory. This is commonly and easiest done by going through the list of loaded modules and reading their export address tables. When shellcode reads the export address tables of ntdll.dll and/or kernel32.dll, EAF detects the shellcode and terminates the process, preventing the exploit from running successfully.

I tested EAF withone of my shellcodes that shows a message in a popup window when it runs successfully. All my shellcodes scan the export address tables of loaded modules to find certain functions. This is a common technique used by almost all shellcodes that EAF is designed to detect. So, enabling EAF should prevent my shellcode, and most others, from working.

I use testival to test my shellcode because it makes things easy to automate and I can copy+paste the output into this blog to show you what is going on. When my shellcode is run, it shows a popup dialog box and then triggers an int3 debugger breakpoint. Here’s the output of w32-testival.exe for a successful run of my shellcode:

C:\Dev\Shellcode\w32-msgbox-shellcode>w32-testival [$]=ascii:w32-msgbox-shellcode.bin eip=$ --verbose --eh --eh
Allocating 0x1000 bytes of memory... ok. (address: 0x00030000)
Setting data and registers:
[0x00030000] = 8C bytes of data.
eax = 0xDEADBEEF (default)
ecx = 0xDEADBEEF (default)
edx = 0xDEADBEEF (default)
ebx = 0xDEADBEEF (default)
esp = ??? (unmodified)
ebp = 0xDEADBEEF (default)
esi = 0xDEADBEEF (default)
edi = 0xDEADBEEF (default)
eip = 0x00030000 ($)
Registering Structured Exception Handler (SEH)...ok.
Registering Vectored Exception Handler (VEH)...ok.
Executing shellcode by jumping to 0x00030000...First chance debugger breakpoint exception at 0x0003008B.
Second chance debugger breakpoint exception at 0x0003008B.
 
C:\Dev\Shellcode\w32-msgbox-shellcode>
 

Of course, you cannot see the popup dialog box in this output, but you can see that the int3 debugger breakpoint at the end of the shellcode was executed. This means that it ran successfully.

After enabling EAF I tried executing the shellcode again and found that it still worked. So either EAF was not working or I was doing something wrong. I contacted my friends at MS who developed the tool and asked them to help me find out what was going on. They explained that in order to install the EAF mitigation, EMET needs to create a new thread in the process first, which means the mitigation is not enabled immediately on startup. To make sure that the mitigation is installed, you need to wait a bit before running your shellcode. So, I added a new switch to testival that allows it to wait a given number of milliseconds before executing the shellcode. Using this new feature, I tried again and this time EAF successfully blocked my shellcode, as you can see here:


C:\Dev\Shellcode\w32-msgbox-shellcode>w32-testival [$]=ascii:w32-msgbox-shellcode.bin eip=$ --verbose --eh --eh --delay=1000
Allocating 0x1000 bytes of memory... ok. (address: 0x00030000)
Setting data and registers:
[0x00030000] = 8C bytes of data.
eax = 0xDEADBEEF (default)
ecx = 0xDEADBEEF (default)
edx = 0xDEADBEEF (default)
ebx = 0xDEADBEEF (default)
esp = ??? (unmodified)
ebp = 0xDEADBEEF (default)
esi = 0xDEADBEEF (default)
edi = 0xDEADBEEF (default)
eip = 0x00030000 ($)
Registering Structured Exception Handler (SEH)...ok.
Registering Vectored Exception Handler (VEH)...ok.
Waiting for 1000 milliseconds...ok.
Executing shellcode by jumping to 0x00030000...First chance single step exception at 0x00030054: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
Second chance exception 0xC0000409 at 0x00030054.
 
C:\Dev\Shellcode\w32-msgbox-shellcode>
 

This time the process is terminated early by a single step exception. This is because the EAF mitigation has detect that the shellcode accessed the export address table and decided to terminate the application rather than allow the shellcode to continue executing. This shows that EAF should be able to detect and protect against exploits that use most common shellcode.

I decided not to reverse EAF in order to find out how it works, but rather try to guess how it works and guess how it might by bypassed and try if it works. I assumed that EAF works by checking the location of the instruction that is accessing the export address table: if it is located inside the code segment of a loaded module, EAF assumes it is valid code and allowed it to continue. Otherwise, EAF assumes it is malicious code and terminates the process. If my hypothesis is correct, it might be possible to have the shellcode use a sequence of instructions inside the code segment of a loaded module that can be used to read memory. Because in this case, EAF will assumes that valid code is attempting to read the export address table rather than my shellcode and allow the code to continue.

To test this, I created a modified version of my shellcode that works like this:

  • First, the shellcode finds out where ntdll.dll is loaded in memory as usual.
  • Second, it finds out where the code segment for ntdll.dll is located.
  • Third, it scans the code segment for a specific instruction sequence that can be used to read arbitrary memory.
  • Finally, it calls this instruction sequence to read the export address table, rather than read it directly.

This is effectively the same as doing a ret-into-libc attack, something which EAF is not designed to block and this should therefore be able to bypass it. It turns out that the RtlGetCurrentPeb function has a useful instruction sequence. This sequence is static across Windows versions and SPs and exactly 4 bytes long, which means it is easy to write code to find it:

ntdll32!RtlGetCurrentPeb:
64a118000000 mov eax,dword ptr fs:[00000018h]
8b4030 mov eax,dword ptr [eax+30h]
c3 ret
 

By setting EAX to the memory address you want to read (minus 0×30) and calling the second instruction, you can read arbitrary memory into EAX.

The first time I tested my code, it was blocked by EAF while scanning for the instruction sequence in the code segment. It turns out that the export address table is located at the start of the code segment of ntdll.dll, so my scan for the instruction sequence was accessing it and triggering EAF. Luckily, the RtlGetCurrentPeb function is not located anywhere near the start of the code segment in any version of ntdll.dll, so it was relatively easy to avoid this by skipping over the first 0×1000 bytes of the code segment.

Here is the result for my modified shellcode, which is only 30 bytes larger than the original:

C:\Dev\Shellcode\w32-msgbox-shellcode>w32-testival [$]=ascii:w32-msgbox-shellcode-eaf.bin eip=$ --verbose --eh --eh --delay=1000
Allocating 0x1000 bytes of memory... ok. (address: 0x00030000)
Setting data and registers:
[0x00030000] = AB bytes of data.
eax = 0xDEADBEEF (default)
ecx = 0xDEADBEEF (default)
edx = 0xDEADBEEF (default)
ebx = 0xDEADBEEF (default)
esp = ??? (unmodified)
ebp = 0xDEADBEEF (default)
esi = 0xDEADBEEF (default)
edi = 0xDEADBEEF (default)
eip = 0x00030000 ($)
Registering Structured Exception Handler (SEH)...ok.
Registering Vectored Exception Handler (VEH)...ok.
Waiting for 1000 milliseconds...ok.
Executing shellcode by jumping to 0x00030000...First chance single step exception at 0x76FBA045: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
First chance single step exception at 0x76FAFFCE: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
First chance single step exception at 0x76FAFFCE: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
First chance single step exception at 0x76FAFFCE: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
<snip>(Many more single step exceptions)<snip>
First chance single step exception at 0x76FAFFCE: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.
First chance debugger breakpoint exception at 0x000300AA.
Second chance debugger breakpoint exception at 0x000300AA.
 
C:\Dev\Shellcode\w32-msgbox-shellcode>
 

Every time the export address table is accessed, the hardware breakpoint is triggered and EAF checks to see if shellcode is attempting to access it. This is why you are seeing so may first change single step exceptions. However, because I am using the instruction sequence in ntdll.dll to access the address table, EAF allows the code to continue. The shellcode runs successfully and the process terminates when the shellcode executes the int3 debugger breakpoint as before.

Have a look at the changes I made to my shellcode to make this work. They are relatively minor and it should be possible to apply this technique to any shellcode, which reduces the usefulness of EAF in the long run.

I thought about possibilities to prevent or detect this bypass. Unfortunately, I could not think of anything that would be effective. Here are some of my ideas and why I think they won’t work:
- EAF could check for this specific instruction sequence. However, shellcode could scan for other sequences in ntdll.dll (or even in other modules) that can be used to achieve the same.
- EAF could “walk the stack” and check that all return addresses are valid. However, the shellcode could construct a ret-into-libc stack with only valid return addresses, where the first call reads the export address table and the next call modifies the return address for the third return address to point to the shellcode again.
- EAF could set additional hardware breakpoints on structures that can be used to find the locations of modules, in an attempt to prevent the shellcode from finding an instruction sequence that it can use to read memory. However, there are a large number of ways in which the location of modules can be found and there are a limited number of hardware breakpoints. There are not enough breakpoints available to protect all of locations that contain sensitive data.

I welcome your thoughts and ideas on this subject.

16 Comments to “Bypassing Export address table Address Filter (EAF)”

  1. Piotr
    2010/11/22

    Yo,

    This EAF protection is not new – well maybe except the new implementation which uses the debug breakpoint. Similar techniques were described in my phrack article (2005). Although at the time i’ve proposed different algorithm which nowadays i consider too slow to apply. Anyway the techniques are similar.

    >They explained that in order to install the EAF mitigation, EMET needs to
    >create a new thread in the process first, which means the mitigation is not
    >enabled immediately on startup.

    Perhaps they had problems with modifying the context of already running thread? :-) First of all i haven’t tested EMET yet but i guess in order to protect all new and already running threads they had to hook NtContinue and re-set the memory breakpoints there (on the other hand they could do the same thing in the exception handler).

    The protection methods you have mentioned will not be really effective. Partially because of the reasons you have listed and partially because the performance would be terrible.

    >EAF could set additional hardware breakpoints on structures that can be
    >used to find the locations of modules,

    i’m using “this” method in one of my “products” although to increase the performance (limit the number of exceptions) i was forced to do some JIT tricks. For example re-compile/link the most heavily used instructions that caused the exceptions. But the method itself is still far from being invincible.

    Summing it up isn’t your bypass method just a variation of return-to-libc* technique?

    Anyway thanks for the entertaining read. cheers.

    - piotr

  2. teach
    2010/11/22

    Very good post. I’m not very comfortable with windows security but i was wondering since there is only 4 hw breakpoints what would happen if the shellcode before trying to access to EAT managed to reset/modifiy the breakpoints addresses? If the GD bit is not set on DR7 anyone could therefore access the hw debug registers, modify them in order to access EAT without the debug handler of EAF being able to notice anything. Of course this is based in the fact that EAF was not enough careful to set the GD flags which is a relatively unverified assumption

  3. SkyLined
    2010/11/22

    @piotr: Yes, you’re right, this is effectively ret-into-libc. I thought I had mentioned that, but apparently I edited that line out later :S. I’ve updated the post to add it back in, thanks!

  4. SkyLined
    2010/11/22

    @teach: I’ve not checked your assumptions but even if what you suggest is possible, it should be easy to fix in the next version of EMET (by setting the GD bit as you suggest). So, it wouldn’t help the attacker much in the long run.

    On the other hand, I believe that the bypass I suggest is much harder, if not impossible to prevent given the design limitations of EAF.

  5. None
    2010/11/22

    Nice post !

    There is however something that I don’t get. Wouldn’t it be easier to call the functions (like NtCreateFile) with their index in the SSDT, instead of using their address ? I guess you would have to put a pointer to your stack in EDX, the index of the function in EAX, and then call SYSENTER.

  6. SkyLined
    2010/11/22

    @None: If you do not need the export address table, then yes: you do not need to bypass EAF :) . However, I don’t see how completely rewriting existing shellcode to do what you suggest is easier than modifying the way it finds functions in DLLs in the way that I suggest. Also, I think you’ll find that you’ll need quite a few more bytes to implement something that works on all versions of Windows and all Service Packs – but feel free to prove me wrong :) .

  7. Piotr
    2010/11/22

    @None:
    If you mean using so called “windows syscall shellcode” i guess this article can give you some additional hints:
    http://bandwidthco.com/sf_whitepapers/windows/Windows%20Syscall%20Shellcode.pdf

    But as SkyLined said, windows syscall shellcodes are not “so” easy to create and they are not so portable since the syscall numbers tend to vary among different service packs and windows systems.

    - piotr

  8. BanMe
    2011/02/24

    What of the relocation table…is that protected by EAF? If not then locating data used by functions to locate functions is all to easy.

  9. SkyLined
    2011/02/26

    EAF protects the Export Address Table, so any pointers to functions outside of the Export Address Table are freely accessible.

  10. nts94
    2012/08/11

    It seems that ms folks have updated their toy

  11. nts94
    2012/08/11

    It seems that ms folks have updated their “toy” and your method doesn’t work with emet 3.0. I wonder which of your mitigations are they using now

Trackbacks & Pingbacks

  1. Форсируем виндовую защиту « Девятый бит

  2. Microsoft shuffles Windows security deck with EMET 2.1 – JailBake

  3. Encrypted Win32 Shellcode Static Analysis | Vadim Kotov

  4. Tweaking Metasploit Modules To Bypass EMET – Part 2 - Security Bits

  5. Windows security Upgrades on OS | Nobody will put Teddybby in a Corner!