Beyond good ol’ Run key, Part 119

October 11, 2019 in Anti-Forensics, Autostart (Persistence)

Pretty much everyone knows about the AEDebug key.

Turns out this key has a twin brother, called AeDebugProtected.

When the WerpGetDebugger function is called, it calls NtQueryInformationProcess to retrieve a basic information about the process. If the process’s basic info data at position 28 (32-bit!) returns a dword value that if masked with 1 is non-zero then AeDebugProtected key is used…

Okay, this is confusing… Let’s step back.

The traditional way of calling NtQueryInformationProcess with ProcessBasicInformation class is typically delivered using a ‘classic’ definition of _PROCESS_BASIC_INFORMATION structure being:

typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

Of course, available source codes online can give us a more precise definition of this structure e.g. Process Hacker source code defines it as:

typedef struct _PROCESS_BASIC_INFORMATION
{
    NTSTATUS ExitStatus;
    PPEB PebBaseAddress;
    ULONG_PTR AffinityMask;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

In both cases the size of a structure is 24 bytes (32-bit system!). When I looked at the code of WerpGetDebugger I was surprised to see that some calls to NtQueryInformationProcess / ProcessBasicInformation rely on a structure that is 32 bytes long!

Okay, so now we know there is some extra information provided by NtQueryInformationProcess to WerpGetDebugger function and that data determines which debug key is being used. Since this ntdll function is simply passed to kernel, I went to look at the code of NtQueryInformationProcess inside ntoskrnl.exe. I quickly discovered that the function does indeed expect a structure that is either 24 or 32 bytes long. Cool.

Continuing my analysis I noticed that the the field at the position 28 is filled in with a result of a call to a function called PsIsProtectedProcess. Protected processes [DOC warning] is a technology described in the past, so not a biggie. And the fact this is what is being checked by the function is of course something we should have expected, given the name used by the Registry Key I mentioned earlier, however… at least we can confirm this with our code analysis…

And here we are with a few conclusive bits:

  • NtQueryInformationProcess / ProcessBasicInformation may use 2 different structure versions!
  • The field at offset 28 (32-bit!) tells us if the process is protected or not. Depending on this, different AeDebug Registry key will be used to launch debugger when the app crashes.

The longer structure can be prototyped as this:

typedef struct _PROCESS_BASIC_INFORMATION_EXT
{
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
ULONG unknown;
ULONG IsProtectedProcess;
} 

At this is how we arrived at Beyond good ol’ Run key, Part 119.

Okay, not quite yet.

The value under this Protected Registry key that the WerpGetDebugger function will use is not Debugger, but ProtectedDebugger. Yup, we are talking:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebugProtected\ProtectedDebugger=<exe>

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…

RIGHT?

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?

No?

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.

However…

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…