A Debugging Approach to Application Verifier

Application Verifier, also known as AppVerifier, is a dynamic instrumentation tool for user mode applications. It is free available from SDK/PSDK, with a set of GUI applications and DLL extensions, plus a good document.

Let's begin by adding the most famous application - notepad.exe - from the appverif.exe GUI, and launch notepad.exe from WinDBG:

windbg.exe notepad.exe

ModLoad: 00620000 00650000 notepad.exe
ModLoad: 77c00000 77d80000 ntdll.dll
Page heap: pid 0xE10: page heap enabled with flags 0x3.
AVRF: notepad.exe: pid 0xE10: flags 0x80643027: application verifier enabled
ModLoad: 10350000 103b0000 C:\Windows\syswow64\verifier.dll
Page heap: pid 0xE10: page heap enabled with flags 0x3.
AVRF: notepad.exe: pid 0xE10: flags 0x80643027: application verifier enabled
ModLoad: 5cca0000 5cccb000 C:\Windows\SysWOW64\vrfcore.dll
ModLoad: 0f820000 0f878000 C:\Windows\SysWOW64\vfbasics.dll
ModLoad: 75330000 75440000 C:\Windows\syswow64\kernel32.dll
ModLoad: 75c40000 75c86000 C:\Windows\syswow64\KERNELBASE.dll
ModLoad: 76ee0000 76f80000 C:\Windows\syswow64\ADVAPI32.dll
ModLoad: 75fd0000 7607c000 C:\Windows\syswow64\msvcrt.dll

Like we've mentioned in A Debugging Approach to IFEO, the loader code in NTDLL knows how to initialize application verifier.

windbg.exe -xe cpr notepad.exe

0:000> sxeld verifier
0:000> g
Page heap: pid 0x1DBC: page heap enabled with flags 0x3.
AVRF: notepad.exe: pid 0x1DBC: flags 0x80643027: application verifier enabled
ModLoad: 105f0000 10650000 C:\Windows\syswow64\verifier.dll
eax=00000000 ebx=77d07e00 ecx=00000000 edx=00000000 esi=7efdd000 edi=00000000
eip=77c1fc42 esp=0018f3a8 ebp=0018f790 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!ZwMapViewOfSection+0x12:
77c1fc42 83c404 add esp,4
0:000> k
ChildEBP RetAddr
0018f3a8 77ca6fa3 ntdll!ZwMapViewOfSection+0x12
0018f790 77ca7c29 ntdll!AvrfMiniLoadDll+0x3d1
0018f7c4 77ca1075 ntdll!AVrfInitializeVerifier+0x252
0018f7fc 77c80759 ntdll!LdrpInitializeApplicationVerifierPackage+0xab
0018f878 77c45383 ntdll!LdrpInitializeExecutionOptions+0x222
0018fa08 77c452d6 ntdll!LdrpInitializeProcess+0x261
0018fa58 77c39e79 ntdll!_LdrpInitialize+0x78
0018fa68 00000000 ntdll!LdrInitializeThunk+0x10

By reading the disassembled code, it's obvious that ntdll!RtlOpenImageFileOptionsKey is used to retrieve the IFEO related information. NTDLL would read from IFEO to see if the application is registered, and whether application verifier is enabled in GlobalFlag (GFLAG). If GFLAG & 0x100 is non-zero, NTDLL would load verifier.dll from %windir%\system32 or %windir%\syswow64, depending on the target bitness.

0:000> sxeld
0:000> g
Page heap: pid 0x1DBC: page heap enabled with flags 0x3.
AVRF: notepad.exe: pid 0x1DBC: flags 0x80643027: application verifier enabled
ModLoad: 0f6f0000 0f71b000 C:\Windows\SysWOW64\vrfcore.dll
eax=00000000 ebx=00000000 ecx=0018f600 edx=0018f601 esi=7efdd000 edi=0018f628
eip=77c1fc42 esp=0018f4fc ebp=0018f550 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!ZwMapViewOfSection+0x12:
77c1fc42 83c404 add esp,4
0:000> k
ChildEBP RetAddr
0018f4fc 77c3beec ntdll!ZwMapViewOfSection+0x12
0018f550 77c3c578 ntdll!LdrpMapViewOfSection+0xc7
0018f644 77c3c3a9 ntdll!LdrpFindOrMapDll+0x333
0018f7c4 77c3c4d5 ntdll!LdrpLoadDll+0x2b2
0018f7fc 77ca746f ntdll!LdrLoadDll+0xaa
0018f850 77ca7aaa ntdll!AVrfpLoadAndInitializeProvider+0x6f
0018f874 77c8117c ntdll!AVrfInitializeVerifier+0xd3
0018fa08 77c452d6 ntdll!LdrpInitializeProcess+0xfba
0018fa58 77c39e79 ntdll!_LdrpInitialize+0x78
0018fa68 00000000 ntdll!LdrInitializeThunk+0x10

NTDLL would further check if IFEO has a REG_SZ value named VerifierDlls. If VerifierDlls is found, it's value will be splitted into DLL names, and these DLLs would be loaded into the target process one by one.

0:000> dt ntdll!IMAGE_DOS_HEADER 0f6f0000
+0x000 e_magic : 0x5a4d
+0x002 e_cblp : 0x90
+0x004 e_cp : 3
+0x006 e_crlc : 0
+0x008 e_cparhdr : 4
+0x00a e_minalloc : 0
+0x00c e_maxalloc : 0xffff
+0x00e e_ss : 0
+0x010 e_sp : 0xb8
+0x012 e_csum : 0
+0x014 e_ip : 0
+0x016 e_cs : 0
+0x018 e_lfarlc : 0x40
+0x01a e_ovno : 0
+0x01c e_res : [4] 0
+0x024 e_oemid : 0
+0x026 e_oeminfo : 0
+0x028 e_res2 : [10] 0
+0x03c e_lfanew : 0n240

0:000> dt ntdll!_IMAGE_NT_HEADERS OptionalHeader.AddressOfEntryPoint 0f6f0000+0n240
+0x018 OptionalHeader :
+0x010 AddressOfEntryPoint : 0x2c86

0:000> ln 0f6f0000+0x2c86
(0f6f2c86) vrfcore!DllMain | (0f642ca7) vrfcore!VerifierOpenLayerProperties
Exact matches:
vrfcore!DllMain = <no type information>

The above steps can be automated using script:

 $$ cdb.exe -xe cpr -c "$$>a< .\appverif.txt" notepad.exe

.echo [Launch Script]

sxeld vrfcore; g; sxdld

$$ get vrfcore.dll base address
r $t1 = vrfcore

.if @$ptrsize == 8 {
    aS IMAGE_NT_HEADERS _IMAGE_NT_HEADERS64
} .else {
    aS IMAGE_NT_HEADERS _IMAGE_NT_HEADERS
}

$$ get OEP offset
.block {
 r $t2 = @@c++(((ntdll!${IMAGE_NT_HEADERS}*)(@$t1 + ((ntdll!_IMAGE_DOS_HEADER*)@$t1)->e_lfanew))->OptionalHeader.AddressOfEntryPoint)
}

$$ break at OEP
bp @$t1 + @$t2; g; bc 0

.echo [Hit OEP]
k

.echo [Arguments]
dd esp L4

Now we've successfully located the OEP (Original Entry Point, we mentioned that in Data Breakpoints) for vrfcore.dll, set a breakpoint.

When we hit the breakpoint on vrfcore!DllMain, take a look at the top frame and it showed the second argument passed in is 4:

DllMain(HINSTANCE hinstDLL = 0f6f0000, DWORD fdwReason= 4, LPVOID lpvReserved)

It looks like fdwReason = 4 is undocumented on MSDN:

  • DLL_PROCESS_DETACH = 0
  • DLL_PROCESS_ATTACH = 1
  • DLL_THREAD_ATTACH = 2
  • DLL_THREAD_DETACH = 3

By looking at the disassembled code, the following instructions looks suspecious:

vsvrfcore!_DllMain:
mov edi, edi
push ebp
mov ebp, esp
mov eax, dword ptr [ebp+0Ch]
push ebx
push esi
xor esi, esi
push edi
xor edi, edi
inc esi
sub eax, edi
je vrfcore!_DllMain+0x426

vrfcore!_DllMain+0x18:
dec eax
je vrfcore!_DllMain+0x2aa

vrfcore!_DllMain+0x1f:
dec eax
je vrfcore!_DllMain+0x29b

vrfcore!_DllMain+0x26:
dec eax
je vrfcore!_DllMain+0x28c

vrfcore!_DllMain+0x2d:
dec eax
jne vrfcore!_DllMain+0x283

vrfcore!_DllMain+0x34:
mov ebx, dword ptr [ebp+10h]
cmp ebx, edi
jne vrfcore!_DllMain+0x79

vrfcore!_DllMain+0x131:
mov edi, offset vrfcore!VfCoreProvider
mov dword ptr [ebx], edi
call vrfcore!VfCoreProviderInitialize

From the highlighted code we can see when fdwReason is 4, the following assignment would happen:

*lpvReserved = (LPVOID)(&vrfcore!VfCoreProvider)

By looking into the Win2003R2 DDK headers, we can find out the layout defintion for Verifier Provider Descript. So it's time to write a small provider now.


 #define WIN32_LEAN_AND_MEAN
#include <Windows.h>

// Borrowed from Win2003R2 DDK

#define DLL_PROCESS_VERIFIER 4

typedef VOID (NTAPI * RTL_VERIFIER_DLL_LOAD_CALLBACK) (PWSTR DllName, PVOID DllBase, SIZE_T DllSize, PVOID Reserved);
typedef VOID (NTAPI * RTL_VERIFIER_DLL_UNLOAD_CALLBACK) (PWSTR DllName, PVOID DllBase, SIZE_T DllSize, PVOID Reserved);
typedef VOID (NTAPI * RTL_VERIFIER_NTDLLHEAPFREE_CALLBACK) (PVOID AllocationBase, SIZE_T AllocationSize);

typedef struct _RTL_VERIFIER_THUNK_DESCRIPTOR {
  PCHAR ThunkName;
  PVOID ThunkOldAddress;
  PVOID ThunkNewAddress;
} RTL_VERIFIER_THUNK_DESCRIPTOR, *PRTL_VERIFIER_THUNK_DESCRIPTOR;

typedef struct _RTL_VERIFIER_DLL_DESCRIPTOR {
  PWCHAR DllName;
  DWORD DllFlags;
  PVOID DllAddress;
  PRTL_VERIFIER_THUNK_DESCRIPTOR DllThunks;
} RTL_VERIFIER_DLL_DESCRIPTOR, *PRTL_VERIFIER_DLL_DESCRIPTOR;

typedef struct _RTL_VERIFIER_PROVIDER_DESCRIPTOR {
  DWORD Length;
  PRTL_VERIFIER_DLL_DESCRIPTOR ProviderDlls;
  RTL_VERIFIER_DLL_LOAD_CALLBACK ProviderDllLoadCallback;
  RTL_VERIFIER_DLL_UNLOAD_CALLBACK ProviderDllUnloadCallback;
  PWSTR VerifierImage;
  DWORD VerifierFlags;
  DWORD VerifierDebug;
  PVOID RtlpGetStackTraceAddress;
  PVOID RtlpDebugPageHeapCreate;
  PVOID RtlpDebugPageHeapDestroy;
  RTL_VERIFIER_NTDLLHEAPFREE_CALLBACK ProviderNtdllHeapFreeCallback;
} RTL_VERIFIER_PROVIDER_DESCRIPTOR, *PRTL_VERIFIER_PROVIDER_DESCRIPTOR;

// ntdll!DbgPrint
typedef ULONG (__cdecl* PFN_DbgPrint)(PCH, ...);
PFN_DbgPrint DbgPrint;

// Here we go
typedef BOOL (WINAPI* PFN_CloseHandle)(HANDLE);
BOOL WINAPI ThunkCloseHandle(HANDLE hObject);

static RTL_VERIFIER_THUNK_DESCRIPTOR aThunks[] = {{"CloseHandle", NULL, ThunkCloseHandle}, {}};
static RTL_VERIFIER_DLL_DESCRIPTOR aDlls[] = {{L"kernel32.dll", 0, NULL, aThunks}, {}};
static RTL_VERIFIER_PROVIDER_DESCRIPTOR vpd = {sizeof(RTL_VERIFIER_PROVIDER_DESCRIPTOR), aDlls};

BOOL WINAPI ThunkCloseHandle(HANDLE hObject)
{
  BOOL fRetVal = ((PFN_CloseHandle)(aThunks[0].ThunkOldAddress))(hObject);
  DbgPrint("CloseHandle(%p) = %s\n", hObject, fRetVal ? "TRUE" : "FALSE");
  return fRetVal;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, PRTL_VERIFIER_PROVIDER_DESCRIPTOR* pVPD)
{
  switch(fdwReason)
  {
  case DLL_PROCESS_ATTACH:
    ::DisableThreadLibraryCalls(hinstDLL);
    break;
  case DLL_PROCESS_DETACH:
    break;
  case DLL_PROCESS_VERIFIER:
    DbgPrint = (PFN_DbgPrint)::GetProcAddress(::GetModuleHandle(TEXT("NTDLL")), "DbgPrint");
    DbgPrint("CommandLine: %s\n", ::GetCommandLineA());
    *pVPD = &vpd;
    break;
  default:
    ::DebugBreak(); // loader lock, be careful!!!
  }
  return TRUE;
}

To compile the source code, use the following command line:

cl.exe avhook.cpp /D UNICODE /GS- /LD /Od /link /ENTRY:DllMain /NODEFAULTLIB /RELEASE /SUBSYSTEM:CONSOLE kernel32.lib

Copy the generated avhook.dll DLL to %windir%\system32 or %windir%\syswow64 folder, depending on the bitness, and import the IFEO entry into registry:


 Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe]
"VerifierDlls"="avhook.dll"
"GlobalFlag"="0x100"
"VerifierFlags"=dword:80000000

Launch notepad.exe from debugger:

windbg.exe notepad.exe

And here is what we got:

 Executable search path is:
ModLoad: 00160000 00190000   notepad.exe
ModLoad: 77070000 771f0000   ntdll.dll
Page heap: pid 0x158C: page heap enabled with flags 0x2.
AVRF: notepad.exe: pid 0x158C: flags 0x48004: application verifier enabled
ModLoad: 714a0000 71500000   C:\Windows\syswow64\verifier.dll Page heap: pid 0x158C: page heap enabled with flags 0x2.
AVRF: notepad.exe: pid 0x158C: flags 0x48004: application verifier enabled
ModLoad: 715c0000 715c5000   C:\Windows\SysWOW64\hook.dll 
...

CloseHandle(00000320) = TRUE
CloseHandle(0000032C) = TRUE
CloseHandle(00000330) = TRUE
CloseHandle(0000034C) = TRUE
CloseHandle(00000328) = TRUE
CloseHandle(000001C8) = TRUE

As you could see, our provider DLL is working as expected :)