Teaching Shellcode New Tricks - DEF CON 25 Addition

Josh Pitts

My REcon Brussels talk of the same title was accepted for DEF CON 25. It was supposed to be a release of x64 bit Import Address Table (IAT) based payload parsing stubs to get them into the Metasploit Framework as a feature. It was supposed to be straight forward, no issues, no surprises kind of talk.

Until June 18th, everything was great.

Then I checked twitter.

Surprise, Surprise! Microsoft is shipping in Windows 10 RS3 not only EMET in the Windows Kernel, but they added an Import Address Filter of sorts. I thought this might happen eventually, but not five months after my initial talk on IAT parsing payloads, and not a month before my talk at DEF CON.

However, I had a potential bypass in mind that I was saving. But I wouldn't know until I downloaded the preview releases.

How does the Import Address Filter Work?

Eventually, after traveling, I was able to get a preview version of Window 10 RS3.

First off, some of these features must be enabled – most are not automatic. For my testing purposes I enabled all of them on the applications that I was testing. As a result, it did stop the payloads I was planning on releasing at DEF CON. *table-flip.gif*

My IAT parser walks the import table looking for the ascii representation of the import name. MS changed the pointer to not point to the name while keeping the location of the API thunk. The thunk must stay intact because compiled code uses it for API calls. If the parser cannot find the import name then it cannot find the API thunk address and this results in a crash. Outside of the Import Name there is nothing static in the import table that will allow easy and reliable (cross platform) import name enumeration on the fly. This is a good fix.

However, they are only filtering on particular API calls, such as LoadLibraryA, GetProcAddress (GPA), and a couple others. Luckily, there is an API that is new to Windows 8+: GetProcAddressForCaller (GPAFC). This works just like GPA except it needs a third parameter set to zero.

GPAFC is imported into Kernel32.dll from Kernelbase.dll, therefore it's in every process (win8+) and it's not filtered by the new Import Address Filter protection. I updated my Def Con talk and notified Microsoft about the bypass since they were making an obvious effort to limit this sort of activity.

Now What?

Userland exploit payloads now cannot meaningfully parse the Import table or the Export Table in Windows 10 if these features are enabled on the target application. There are two options:

  1. Try to find another Windows API in the IAT that will leak information to help enumerate the GPA API thunk.

  2. Don't parse the Import Table and Export Table - use APIs in the Import Table directly like compiled code.

Let's look at number two and think about it. We've been spoiled for a long time with shellcode based payloads that work everywhere all the time, across OS versions, applications, and in places where shellcode doesn't actually have to be used (Powershell, VBA, C#, compiled code, etc...). All the while, exploits have been getting more focused and customized down to application versions. The same bug can exist across several application versions, but as part of the exploit delivery there must be some application version enumeration (and even OS enumeration) to deliver the right exploit package for the target. This could be accomplished for shellcode development also, by customizing the shellcode to the application. Depending on the process environment, the shellcode could work across several versions.

How would this work?

GPA is all that is needed to build a payload and GPA can be in the main module or any loaded module (DLL), however, application specific DLLs should be targeted, not OS specific DLLs (for portability). For example, during exploit development a DLL that shipped with Firefox version 45.0 would be targeted, and the GPA offset from image base in that DLL would be used. The same could be done with Firefox.exe itself, if GPA existed in its import table. Either way, once the exploit executes and the payload is in a useable state, to find the address in memory of GPA (in the main module/executable) is simple: PEB.imagebase + GPA_offset.

The code to do this is less than that of parsing the import table.

Import Table parsing stub (GPA in main module):

Known GPA offset stub (GPA in main module):

One could take this concept further with more engineering. For example, if there is no consistent known GPA offset between application versions in the target application or application specific DLLs, one could diff a target DLL between versions, track the location of the GPA offsets for each version, incorporate that in a diff table which would be used to identify the application version the payload is in and then use the corresponding GPA offset. The logic to do this should be no more than ten lines of assembly.

Building the infrastructure to do the diffing across application versions (all binaries) to output a payload would take a single competent developer no more than a couple weeks to develop.

All in all, this entire process has been a fun and I'm happy to see Microsoft pushing out more anti-exploit mitigations. For my updated slides and code from DEF CON 25, see my personal github repo.

Josh Pitts
Principal Hacker, Offsec Team

Josh Pitts is a Principal Hacker at Okta on our offsec team. He has over 15 years' experience conducting physical and IT security assessments, IT security operations support, penetration testing, malware analysis, reverse engineering, and forensics. Josh also served in the US Marines working in SIGINT.