The risk of falling for a system32 blasé

October 10, 2019 in Archaeology, Undocumented Windows Internals

Yeah, system32… who cares… so old, so over-explored… nothing to see here…


Welcome to a post that will ask some questions that will possibly make you ask even more questions.

Have you ever… and I mean.. like… ever… tried to compare the following directories:

  • System32 on a 32- and a 64-bit system (same OS version)
  • System32 on a 64-bit system vs. SysWOW64 on a very same 64-bit system?


Welcome… to a system32 blasé effect.

We take system32 for granted. It’s there, it has its own vulns. it has its own features, and we (most of us really) assume that it’s a monolith structure that is just there and never changes, except for the major OS releases.


Putting patches aside, the system32 folder has multiple versions. I alluded to it in my earlier question, but it is a matter-of-fact that you will find different versions of system32 folder not only on different versions of Windows OS, but also… the very same versions as well. This is because they differ not only by a version number, but also by their architectural ‘geoposition’…

Let me explain…

When you compare a list of exports from a kernel32.dll that is taken from a 64-bit OS against a 32-bit version of OS (e.g. from Windows 10, same version) you will quickly notice that the 64-bit version exports more APIs. However. the 32-bit version exports a number of APIs that are not exported by 64-bit version either. So… We are talking two different metadata sets… and one is not a subset of another.

The 32-bit version has the following additional APIs not present in a kernel32.dll compiled for 64-bit:

  • CreateSocketHandle
  • GetHandleContext
  • InterlockedCompareExchange
  • InterlockedCompareExchange64
  • InterlockedDecrement
  • InterlockedExchange
  • InterlockedExchangeAdd
  • InterlockedIncrement
  • SetHandleContext

And the 32-bit kernel32.bit is missing the following APIs that are present inside a 64-bit kernel32.dll:

  • __C_specific_handler
  • __chkstk
  • CreateUmsCompletionList
  • CreateUmsThreadContext
  • DeleteUmsCompletionList
  • DeleteUmsThreadContext
  • DequeueUmsCompletionListItems
  • EnterUmsSchedulingMode
  • ExecuteUmsThread
  • GetCurrentUmsThread
  • GetNextUmsListItem
  • GetUmsCompletionListEvent
  • GetUmsSystemThreadInformation
  • _local_unwind
  • __misaligned_access
  • QueryUmsThreadInformation
  • RtlAddFunctionTable
  • RtlCompareMemory
  • RtlCopyMemory
  • RtlDeleteFunctionTable
  • RtlInstallFunctionTableCallback
  • RtlLookupFunctionEntry
  • RtlRaiseException
  • RtlRestoreContext
  • RtlUnwindEx
  • RtlVirtualUnwind
  • SetUmsThreadInformation
  • uaw_lstrcmpiW
  • uaw_lstrcmpW
  • uaw_lstrlenW
  • uaw_wcschr
  • uaw_wcscpy
  • uaw_wcsicmp
  • uaw_wcslen
  • uaw_wcsrchr
  • UmsThreadYield

You may think that it’s okay, nothing to see here. It’s obvious that different architectures will ask for different exported APIs, different handling of some system events, etc.

But again… however…

You need to look at all these files: DLLs, EXEs, etc. as completely different binary compositions. They follow a different logic, they obey different rules, and can be vulnerable to completely different set of attacks. Yes. There is a code inside a 32-bit kernel32.dll that you won’t find in its 64-bit equivalent, and vice versa. Repeat after me: These. Are. 2. Different. Dlls. Cousins, but not twins. Apply the very same logic to every single common OS DLL name you know.

And to be more specific… when you compare the allegedly same code base between 32- and 64-bits you will find lots of differences. The WOW64 layer, and associated with it Registry Entries that are very expected to be there are one thing, but there is definitely more. Decompiling both binaries and looking at their string references shows that many of 32-bit references are simply gone inside a 64-bit version. The 64-bit version is obviously stricter, and less concerned about compatibility-down, hence the code is defo simplified…

Comments are closed.