2 * PROJECT: ReactOS Application compatibility module
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Shim engine core
5 * COPYRIGHT: Copyright 2015-2018 Mark Jansen (mark.jansen@reactos.org)
8 #define WIN32_NO_STATUS
13 /* Make sure we don't include apphelp logging */
14 #define APPHELP_NOSDBPAPI
20 FARPROC WINAPI
StubGetProcAddress(HINSTANCE hModule
, LPCSTR lpProcName
);
21 BOOL WINAPI
SE_IsShimDll(PVOID BaseAddress
);
24 extern HMODULE g_hInstance
;
25 static UNICODE_STRING g_WindowsDirectory
;
26 static UNICODE_STRING g_System32Directory
;
27 static UNICODE_STRING g_SxsDirectory
;
28 ULONG g_ShimEngDebugLevel
= 0xffffffff;
29 BOOL g_bComPlusImage
= FALSE
;
30 BOOL g_bShimDuringInit
= FALSE
;
31 BOOL g_bInternalHooksUsed
= FALSE
;
32 static ARRAY g_pShimInfo
; /* PSHIMMODULE */
33 static ARRAY g_pHookArray
; /* HOOKMODULEINFO */
34 static ARRAY g_InExclude
; /* INEXCLUDE */
36 /* If we have setup a hook for a function, we should also redirect GetProcAddress for this function */
37 HOOKAPIEX g_IntHookEx
[] =
40 "kernel32.dll", /* LibraryName */
41 "GetProcAddress", /* FunctionName */
42 StubGetProcAddress
, /* ReplacementFunction*/
43 NULL
, /* OriginalFunction */
49 static inline BOOL
ARRAY_InitWorker(PARRAY Array
, DWORD ItemSize
)
52 Array
->Size__
= Array
->MaxSize__
= 0;
53 Array
->ItemSize__
= ItemSize
;
58 static inline BOOL
ARRAY_EnsureSize(PARRAY Array
, DWORD ItemSize
, DWORD GrowWith
)
64 ASSERT(ItemSize
== Array
->ItemSize__
);
66 if (Array
->MaxSize__
> Array
->Size__
)
69 Count
= Array
->Size__
+ GrowWith
;
70 pNewData
= SeiAlloc(Count
* ItemSize
);
74 SHIMENG_FAIL("Failed to allocate %d bytes\n", Count
* ItemSize
);
77 Array
->MaxSize__
= Count
;
81 memcpy(pNewData
, Array
->Data__
, Array
->Size__
* ItemSize
);
82 SeiFree(Array
->Data__
);
84 Array
->Data__
= pNewData
;
89 static inline PVOID
ARRAY_AppendWorker(PARRAY Array
, DWORD ItemSize
, DWORD GrowWith
)
93 if (!ARRAY_EnsureSize(Array
, ItemSize
, GrowWith
))
96 pData
= Array
->Data__
;
97 pData
+= (Array
->Size__
* ItemSize
);
103 static inline PVOID
ARRAY_AtWorker(PARRAY Array
, DWORD ItemSize
, DWORD n
)
108 ASSERT(ItemSize
== Array
->ItemSize__
);
109 ASSERT(n
< Array
->Size__
);
111 pData
= Array
->Data__
;
112 return pData
+ (n
* ItemSize
);
116 #define ARRAY_Init(Array, TypeOfArray) ARRAY_InitWorker((Array), sizeof(TypeOfArray))
117 #define ARRAY_Append(Array, TypeOfArray) (TypeOfArray*)ARRAY_AppendWorker((Array), sizeof(TypeOfArray), 5)
118 #define ARRAY_At(Array, TypeOfArray, at) (TypeOfArray*)ARRAY_AtWorker((Array), sizeof(TypeOfArray), at)
119 #define ARRAY_Size(Array) (Array)->Size__
122 VOID
SeiInitDebugSupport(VOID
)
124 static const UNICODE_STRING DebugKey
= RTL_CONSTANT_STRING(L
"SHIMENG_DEBUG_LEVEL");
125 UNICODE_STRING DebugValue
;
127 ULONG NewLevel
= SEI_MSG
; /* Show some basic info in the logs, unless configured different */
130 RtlInitEmptyUnicodeString(&DebugValue
, Buffer
, sizeof(Buffer
));
132 Status
= RtlQueryEnvironmentVariable_U(NULL
, &DebugKey
, &DebugValue
);
134 if (NT_SUCCESS(Status
))
136 if (!NT_SUCCESS(RtlUnicodeStringToInteger(&DebugValue
, 10, &NewLevel
)))
139 g_ShimEngDebugLevel
= NewLevel
;
144 * Outputs diagnostic info.
146 * @param [in] Level The level to log this message with, choose any of [SHIM_ERR,
147 * SHIM_WARN, SHIM_INFO].
148 * @param [in] FunctionName The function this log should be attributed to.
149 * @param [in] Format The format string.
150 * @param ... Variable arguments providing additional information.
152 * @return Success: TRUE Failure: FALSE.
154 BOOL WINAPIV
SeiDbgPrint(SEI_LOG_LEVEL Level
, PCSTR Function
, PCSTR Format
, ...)
157 char* Current
= Buffer
;
158 const char* LevelStr
;
159 size_t Length
= sizeof(Buffer
);
163 if (g_ShimEngDebugLevel
== 0xffffffff)
164 SeiInitDebugSupport();
166 if (Level
> g_ShimEngDebugLevel
)
189 hr
= StringCchPrintfExA(Current
, Length
, &Current
, &Length
, STRSAFE_NULL_ON_FAILURE
, "[%s] [%s] ", LevelStr
, Function
);
191 hr
= StringCchPrintfExA(Current
, Length
, &Current
, &Length
, STRSAFE_NULL_ON_FAILURE
, "[%s] ", LevelStr
);
196 va_start(ArgList
, Format
);
197 hr
= StringCchVPrintfExA(Current
, Length
, &Current
, &Length
, STRSAFE_NULL_ON_FAILURE
, Format
, ArgList
);
202 DbgPrint("%s", Buffer
);
207 PVOID
SeiGetModuleFromAddress(PVOID addr
)
209 PVOID hModule
= NULL
;
210 RtlPcToFileHeader(addr
, &hModule
);
216 /* TODO: Guard against recursive calling / calling init multiple times! */
217 VOID
NotifyShims(DWORD dwReason
, PVOID Info
)
221 for (n
= 0; n
< ARRAY_Size(&g_pShimInfo
); ++n
)
223 PSHIMMODULE pShimModule
= *ARRAY_At(&g_pShimInfo
, PSHIMMODULE
, n
);
224 if (!pShimModule
->pNotifyShims
)
227 pShimModule
->pNotifyShims(dwReason
, Info
);
233 VOID
SeiCheckComPlusImage(PVOID BaseAddress
)
235 ULONG ComSectionSize
;
236 g_bComPlusImage
= RtlImageDirectoryEntryToData(BaseAddress
, TRUE
, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
, &ComSectionSize
) != NULL
;
238 SHIMENG_INFO("COM+ executable %s\n", g_bComPlusImage
? "TRUE" : "FALSE");
242 PSHIMMODULE
SeiGetShimModuleInfo(PVOID BaseAddress
)
246 for (n
= 0; n
< ARRAY_Size(&g_pShimInfo
); ++n
)
248 PSHIMMODULE pShimModule
= *ARRAY_At(&g_pShimInfo
, PSHIMMODULE
, n
);
250 if (pShimModule
->BaseAddress
== BaseAddress
)
256 PSHIMMODULE
SeiCreateShimModuleInfo(PCWSTR DllName
, PVOID BaseAddress
)
258 static const ANSI_STRING GetHookAPIs
= RTL_CONSTANT_STRING("GetHookAPIs");
259 static const ANSI_STRING NotifyShims
= RTL_CONSTANT_STRING("NotifyShims");
260 PSHIMMODULE
* pData
, Data
;
261 PVOID pGetHookAPIs
, pNotifyShims
;
263 if (!NT_SUCCESS(LdrGetProcedureAddress(BaseAddress
, (PANSI_STRING
)&GetHookAPIs
, 0, &pGetHookAPIs
)) ||
264 !NT_SUCCESS(LdrGetProcedureAddress(BaseAddress
, (PANSI_STRING
)&NotifyShims
, 0, &pNotifyShims
)))
266 SHIMENG_WARN("Failed to resolve entry points for %S\n", DllName
);
270 pData
= ARRAY_Append(&g_pShimInfo
, PSHIMMODULE
);
274 *pData
= SeiAlloc(sizeof(SHIMMODULE
));
278 RtlCreateUnicodeString(&Data
->Name
, DllName
);
279 Data
->BaseAddress
= BaseAddress
;
281 Data
->pGetHookAPIs
= pGetHookAPIs
;
282 Data
->pNotifyShims
= pNotifyShims
;
284 ARRAY_Init(&Data
->EnabledShims
, PSHIMINFO
);
289 PSHIMINFO
SeiAppendHookInfo(PSHIMMODULE pShimModuleInfo
, PHOOKAPIEX pHookApi
, DWORD dwHookCount
, PCWSTR ShimName
)
291 PSHIMINFO
* pData
, Data
;
293 pData
= ARRAY_Append(&pShimModuleInfo
->EnabledShims
, PSHIMINFO
);
297 *pData
= SeiAlloc(sizeof(SHIMINFO
));
303 Data
->ShimName
= SdbpStrDup(ShimName
);
307 Data
->pHookApi
= pHookApi
;
308 Data
->dwHookCount
= dwHookCount
;
309 Data
->pShimModule
= pShimModuleInfo
;
310 ARRAY_Init(&Data
->InExclude
, INEXCLUDE
);
314 PHOOKMODULEINFO
SeiFindHookModuleInfo(PUNICODE_STRING ModuleName
, PVOID BaseAddress
)
318 if (ModuleName
== NULL
&& BaseAddress
== NULL
)
320 BaseAddress
= NtCurrentPeb()->ImageBaseAddress
;
323 for (n
= 0; n
< ARRAY_Size(&g_pHookArray
); ++n
)
325 PHOOKMODULEINFO pModuleInfo
= ARRAY_At(&g_pHookArray
, HOOKMODULEINFO
, n
);
327 if (BaseAddress
&& BaseAddress
== pModuleInfo
->BaseAddress
)
330 if (!BaseAddress
&& RtlEqualUnicodeString(ModuleName
, &pModuleInfo
->Name
, TRUE
))
337 PHOOKMODULEINFO
SeiFindHookModuleInfoForImportDescriptor(PBYTE DllBase
, PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor
)
339 UNICODE_STRING DllName
;
343 if (!RtlCreateUnicodeStringFromAsciiz(&DllName
, (PCSZ
)(DllBase
+ ImportDescriptor
->Name
)))
345 SHIMENG_FAIL("Unable to convert dll name to unicode\n");
349 Success
= LdrGetDllHandle(NULL
, NULL
, &DllName
, &DllHandle
);
350 RtlFreeUnicodeString(&DllName
);
352 if (!NT_SUCCESS(Success
))
354 SHIMENG_FAIL("Unable to get module handle for %wZ\n", &DllName
);
358 return SeiFindHookModuleInfo(NULL
, DllHandle
);
361 static LPCWSTR
SeiGetStringPtr(PDB pdb
, TAGID tag
, TAG type
)
363 TAGID tagEntry
= SdbFindFirstTag(pdb
, tag
, type
);
364 if (tagEntry
== TAGID_NULL
)
367 return SdbGetStringTagPtr(pdb
, tagEntry
);
370 static DWORD
SeiGetDWORD(PDB pdb
, TAGID tag
, TAG type
)
372 TAGID tagEntry
= SdbFindFirstTag(pdb
, tag
, type
);
373 if (tagEntry
== TAGID_NULL
)
376 return SdbReadDWORDTag(pdb
, tagEntry
, 0);
379 static QWORD
SeiGetQWORD(PDB pdb
, TAGID tag
, TAG type
)
381 TAGID tagEntry
= SdbFindFirstTag(pdb
, tag
, type
);
382 if (tagEntry
== TAGID_NULL
)
385 return SdbReadQWORDTag(pdb
, tagEntry
, 0);
388 static VOID
SeiAddShim(TAGREF trShimRef
, PARRAY pShimRef
)
392 Data
= ARRAY_Append(pShimRef
, TAGREF
);
399 static VOID
SeiAddFlag(PDB pdb
, TAGID tiFlagRef
, PFLAGINFO pFlagInfo
)
403 /* Resolve the FLAG_REF to the real FLAG node */
404 TAGID FlagTag
= SeiGetDWORD(pdb
, tiFlagRef
, TAG_FLAG_TAGID
);
406 if (FlagTag
== TAGID_NULL
)
409 pFlagInfo
->AppCompatFlags
.QuadPart
|= SeiGetQWORD(pdb
, FlagTag
, TAG_FLAG_MASK_KERNEL
);
410 pFlagInfo
->AppCompatFlagsUser
.QuadPart
|= SeiGetQWORD(pdb
, FlagTag
, TAG_FLAG_MASK_USER
);
411 Flag
.QuadPart
= SeiGetQWORD(pdb
, FlagTag
, TAG_FLAG_PROCESSPARAM
);
412 pFlagInfo
->ProcessParameters_Flags
|= Flag
.LowPart
;
415 /* Propagate layers to child processes */
416 static VOID
SeiSetLayerEnvVar(LPCWSTR wszLayer
)
419 UNICODE_STRING VarName
= RTL_CONSTANT_STRING(L
"__COMPAT_LAYER");
420 UNICODE_STRING Value
;
422 RtlInitUnicodeString(&Value
, wszLayer
);
424 Status
= RtlSetEnvironmentVariable(NULL
, &VarName
, &Value
);
425 if (NT_SUCCESS(Status
))
426 SHIMENG_INFO("Set env var %wZ=%wZ\n", &VarName
, &Value
);
428 SHIMENG_FAIL("Failed to set %wZ: 0x%x\n", &VarName
, Status
);
431 #define MAX_LAYER_LENGTH 256
433 /* Translate all Exe and Layer entries to Shims, and propagate all layers */
434 static VOID
SeiBuildShimRefArray(HSDB hsdb
, SDBQUERYRESULT
* pQuery
, PARRAY pShimRef
, PFLAGINFO pFlagInfo
)
436 WCHAR wszLayerEnvVar
[MAX_LAYER_LENGTH
] = { 0 };
439 for (n
= 0; n
< pQuery
->dwExeCount
; ++n
)
443 if (SdbTagRefToTagID(hsdb
, pQuery
->atrExes
[n
], &pdb
, &tag
))
445 LPCWSTR ExeName
= SeiGetStringPtr(pdb
, tag
, TAG_NAME
);
446 TAGID ShimRef
= SdbFindFirstTag(pdb
, tag
, TAG_SHIM_REF
);
447 TAGID FlagRef
= SdbFindFirstTag(pdb
, tag
, TAG_FLAG_REF
);
450 SeiDbgPrint(SEI_MSG
, NULL
, "ShimInfo(Exe(%S))\n", ExeName
);
452 while (ShimRef
!= TAGID_NULL
)
455 if (SdbTagIDToTagRef(hsdb
, pdb
, ShimRef
, &trShimRef
))
456 SeiAddShim(trShimRef
, pShimRef
);
458 ShimRef
= SdbFindNextTag(pdb
, tag
, ShimRef
);
461 while (FlagRef
!= TAGID_NULL
)
463 SeiAddFlag(pdb
, FlagRef
, pFlagInfo
);
465 FlagRef
= SdbFindNextTag(pdb
, tag
, FlagRef
);
471 for (n
= 0; n
< pQuery
->dwLayerCount
; ++n
)
475 if (SdbTagRefToTagID(hsdb
, pQuery
->atrLayers
[n
], &pdb
, &tag
))
477 LPCWSTR LayerName
= SeiGetStringPtr(pdb
, tag
, TAG_NAME
);
478 TAGID ShimRef
= SdbFindFirstTag(pdb
, tag
, TAG_SHIM_REF
);
479 TAGID FlagRef
= SdbFindFirstTag(pdb
, tag
, TAG_FLAG_REF
);
484 SeiDbgPrint(SEI_MSG
, NULL
, "ShimInfo(Layer(%S))\n", LayerName
);
485 if (wszLayerEnvVar
[0])
486 StringCchCatW(wszLayerEnvVar
, ARRAYSIZE(wszLayerEnvVar
), L
" ");
487 hr
= StringCchCatW(wszLayerEnvVar
, ARRAYSIZE(wszLayerEnvVar
), LayerName
);
490 SHIMENG_FAIL("Unable to append %S\n", LayerName
);
494 while (ShimRef
!= TAGID_NULL
)
497 if (SdbTagIDToTagRef(hsdb
, pdb
, ShimRef
, &trShimRef
))
498 SeiAddShim(trShimRef
, pShimRef
);
500 ShimRef
= SdbFindNextTag(pdb
, tag
, ShimRef
);
503 while (FlagRef
!= TAGID_NULL
)
505 SeiAddFlag(pdb
, FlagRef
, pFlagInfo
);
507 FlagRef
= SdbFindNextTag(pdb
, tag
, FlagRef
);
511 if (wszLayerEnvVar
[0])
512 SeiSetLayerEnvVar(wszLayerEnvVar
);
515 /* Given the hooks from one shim, find the relevant modules and store the combination of module + hook */
516 VOID
SeiAddHooks(PHOOKAPIEX hooks
, DWORD dwHookCount
, PSHIMINFO pShim
)
519 UNICODE_STRING UnicodeModName
;
522 RtlInitEmptyUnicodeString(&UnicodeModName
, Buf
, sizeof(Buf
));
524 for (n
= 0; n
< dwHookCount
; ++n
)
526 ANSI_STRING AnsiString
;
528 PHOOKAPIEX hook
= hooks
+ n
;
529 PHOOKAPIEX
* pHookApi
;
530 PHOOKMODULEINFO HookModuleInfo
;
532 RtlInitAnsiString(&AnsiString
, hook
->LibraryName
);
533 if (!NT_SUCCESS(RtlAnsiStringToUnicodeString(&UnicodeModName
, &AnsiString
, FALSE
)))
535 SHIMENG_FAIL("Unable to convert %s to Unicode\n", hook
->LibraryName
);
539 RtlInitAnsiString(&AnsiString
, hook
->FunctionName
);
540 if (NT_SUCCESS(LdrGetDllHandle(NULL
, 0, &UnicodeModName
, &DllHandle
)))
545 if (!NT_SUCCESS(LdrGetProcedureAddress(DllHandle
, &AnsiString
, 0, &ProcAddress
)))
547 SHIMENG_FAIL("Unable to retrieve %s!%s\n", hook
->LibraryName
, hook
->FunctionName
);
551 HookModuleInfo
= SeiFindHookModuleInfo(NULL
, DllHandle
);
552 hook
->OriginalFunction
= ProcAddress
;
556 HookModuleInfo
= SeiFindHookModuleInfo(&UnicodeModName
, NULL
);
562 HookModuleInfo
= ARRAY_Append(&g_pHookArray
, HOOKMODULEINFO
);
566 HookModuleInfo
->BaseAddress
= DllHandle
;
567 ARRAY_Init(&HookModuleInfo
->HookApis
, PHOOKAPIEX
);
568 RtlCreateUnicodeString(&HookModuleInfo
->Name
, UnicodeModName
.Buffer
);
571 hook
->pShimInfo
= pShim
;
573 for (j
= 0; j
< ARRAY_Size(&HookModuleInfo
->HookApis
); ++j
)
575 PHOOKAPIEX HookApi
= *ARRAY_At(&HookModuleInfo
->HookApis
, PHOOKAPIEX
, j
);
576 int CmpResult
= strcmp(hook
->FunctionName
, HookApi
->FunctionName
);
579 /* Multiple hooks on one function? --> use ApiLink */
580 SHIMENG_FAIL("Multiple hooks on one API is not yet supported!\n");
584 pHookApi
= ARRAY_Append(&HookModuleInfo
->HookApis
, PHOOKAPIEX
);
589 typedef FARPROC(WINAPI
* GETPROCADDRESSPROC
)(HINSTANCE
, LPCSTR
);
591 /* Check if we should fake the return from GetProcAddress (because we also redirected the iat for this module) */
592 FARPROC WINAPI
StubGetProcAddress(HINSTANCE hModule
, LPCSTR lpProcName
)
594 char szOrdProcName
[10] = "";
595 LPCSTR lpPrintName
= lpProcName
;
596 PVOID Addr
= _ReturnAddress();
597 PHOOKMODULEINFO HookModuleInfo
;
598 FARPROC proc
= ((GETPROCADDRESSPROC
)g_IntHookEx
[0].OriginalFunction
)(hModule
, lpProcName
);
600 if (!HIWORD(lpProcName
))
602 sprintf(szOrdProcName
, "#%lu", (DWORD
)lpProcName
);
603 lpPrintName
= szOrdProcName
;
606 Addr
= SeiGetModuleFromAddress(Addr
);
607 if (SE_IsShimDll(Addr
))
609 SHIMENG_MSG("Not touching GetProcAddress for shim dll (%p!%s)", hModule
, lpPrintName
);
613 SHIMENG_INFO("(GetProcAddress(%p!%s) => %p\n", hModule
, lpPrintName
, proc
);
615 HookModuleInfo
= SeiFindHookModuleInfo(NULL
, hModule
);
617 /* FIXME: Ordinal not yet supported */
618 if (HookModuleInfo
&& HIWORD(lpProcName
))
621 for (n
= 0; n
< ARRAY_Size(&HookModuleInfo
->HookApis
); ++n
)
623 PHOOKAPIEX HookApi
= *ARRAY_At(&HookModuleInfo
->HookApis
, PHOOKAPIEX
, n
);
624 int CmpResult
= strcmp(lpProcName
, HookApi
->FunctionName
);
627 SHIMENG_MSG("Redirecting %p to %p\n", proc
, HookApi
->ReplacementFunction
);
628 proc
= HookApi
->ReplacementFunction
;
637 /* Walk all shim modules / enabled shims, and add their hooks */
638 VOID
SeiResolveAPIs(VOID
)
642 /* Enumerate all Shim modules */
643 for (mod
= 0; mod
< ARRAY_Size(&g_pShimInfo
); ++mod
)
645 PSHIMMODULE pShimModule
= *ARRAY_At(&g_pShimInfo
, PSHIMMODULE
, mod
);
646 DWORD dwShimCount
= ARRAY_Size(&pShimModule
->EnabledShims
);
648 /* Enumerate all Shims */
649 for (n
= 0; n
< dwShimCount
; ++n
)
651 PSHIMINFO pShim
= *ARRAY_At(&pShimModule
->EnabledShims
, PSHIMINFO
, n
);
653 PHOOKAPIEX hooks
= pShim
->pHookApi
;
654 DWORD dwHookCount
= pShim
->dwHookCount
;
656 SeiAddHooks(hooks
, dwHookCount
, pShim
);
661 /* If we hooked something, we should also redirect GetProcAddress */
662 VOID
SeiAddInternalHooks(DWORD dwNumHooks
)
666 g_bInternalHooksUsed
= FALSE
;
670 SeiAddHooks(g_IntHookEx
, ARRAYSIZE(g_IntHookEx
), NULL
);
671 g_bInternalHooksUsed
= TRUE
;
674 /* Patch one function in the iat */
675 VOID
SeiPatchNewImport(PIMAGE_THUNK_DATA FirstThunk
, PHOOKAPIEX HookApi
, PLDR_DATA_TABLE_ENTRY LdrEntry
)
677 ULONG OldProtection
= 0;
682 SHIMENG_INFO("Hooking API \"%s!%s\" for DLL \"%wZ\"\n", HookApi
->LibraryName
, HookApi
->FunctionName
, &LdrEntry
->BaseDllName
);
684 Ptr
= &FirstThunk
->u1
.Function
;
685 Size
= sizeof(FirstThunk
->u1
.Function
);
686 Status
= NtProtectVirtualMemory(NtCurrentProcess(), &Ptr
, &Size
, PAGE_EXECUTE_READWRITE
, &OldProtection
);
688 if (!NT_SUCCESS(Status
))
690 SHIMENG_FAIL("Unable to unprotect 0x%p\n", &FirstThunk
->u1
.Function
);
694 SHIMENG_INFO("changing 0x%p to 0x%p\n", FirstThunk
->u1
.Function
, HookApi
->ReplacementFunction
);
696 FirstThunk
->u1
.Function
= (ULONGLONG
)HookApi
->ReplacementFunction
;
698 FirstThunk
->u1
.Function
= (DWORD
)HookApi
->ReplacementFunction
;
701 Size
= sizeof(FirstThunk
->u1
.Function
);
702 Status
= NtProtectVirtualMemory(NtCurrentProcess(), &Ptr
, &Size
, OldProtection
, &OldProtection
);
704 if (!NT_SUCCESS(Status
))
706 SHIMENG_WARN("Unable to reprotect 0x%p\n", &FirstThunk
->u1
.Function
);
711 PINEXCLUDE
SeiFindInExclude(PARRAY InExclude
, PCUNICODE_STRING DllName
)
715 for (n
= 0; n
< ARRAY_Size(InExclude
); ++n
)
717 PINEXCLUDE InEx
= ARRAY_At(InExclude
, INEXCLUDE
, n
);
719 if (RtlEqualUnicodeString(&InEx
->Module
, DllName
, TRUE
))
726 BOOL
SeiIsExcluded(PLDR_DATA_TABLE_ENTRY LdrEntry
, PHOOKAPIEX HookApi
)
728 PSHIMINFO pShimInfo
= HookApi
->pShimInfo
;
729 PINEXCLUDE InExclude
;
730 BOOL IsExcluded
= FALSE
;
734 /* Internal hook, do not exclude it */
738 /* By default, everything from System32 or WinSxs is excluded */
739 if (RtlPrefixUnicodeString(&g_System32Directory
, &LdrEntry
->FullDllName
, TRUE
) ||
740 RtlPrefixUnicodeString(&g_SxsDirectory
, &LdrEntry
->FullDllName
, TRUE
))
743 InExclude
= SeiFindInExclude(&pShimInfo
->InExclude
, &LdrEntry
->BaseDllName
);
746 /* If it is on the 'exclude' list, bail out */
747 if (!InExclude
->Include
)
749 SHIMENG_INFO("Module '%wZ' excluded for shim %S, API '%s!%s', because it on in the exclude list.\n",
750 &LdrEntry
->BaseDllName
, pShimInfo
->ShimName
, HookApi
->LibraryName
, HookApi
->FunctionName
);
754 /* If it is on the 'include' list, override System32 / Winsxs check. */
757 SHIMENG_INFO("Module '%wZ' included for shim %S, API '%s!%s', because it is on the include list.\n",
758 &LdrEntry
->BaseDllName
, pShimInfo
->ShimName
, HookApi
->LibraryName
, HookApi
->FunctionName
);
766 SHIMENG_INFO("Module '%wZ' excluded for shim %S, API '%s!%s', because it is in System32/WinSXS.\n",
767 &LdrEntry
->BaseDllName
, pShimInfo
->ShimName
, HookApi
->LibraryName
, HookApi
->FunctionName
);
773 VOID
SeiAppendInExclude(PARRAY dest
, PCWSTR ModuleName
, BOOL IsInclude
)
775 PINEXCLUDE InExclude
;
776 UNICODE_STRING ModuleNameU
;
777 RtlInitUnicodeString(&ModuleNameU
, ModuleName
);
779 InExclude
= SeiFindInExclude(dest
, &ModuleNameU
);
782 InExclude
->Include
= IsInclude
;
786 InExclude
= ARRAY_Append(dest
, INEXCLUDE
);
789 PCWSTR ModuleNameCopy
= SdbpStrDup(ModuleName
);
790 RtlInitUnicodeString(&InExclude
->Module
, ModuleNameCopy
);
791 InExclude
->Include
= IsInclude
;
795 /* Read the INEXCLUD tags from a given parent tag */
796 VOID
SeiReadInExclude(PDB pdb
, TAGID parent
, PARRAY dest
)
800 InExcludeTag
= SdbFindFirstTag(pdb
, parent
, TAG_INEXCLUD
);
802 while (InExcludeTag
!= TAGID_NULL
)
805 TAGID ModuleTag
= SdbFindFirstTag(pdb
, InExcludeTag
, TAG_MODULE
);
806 TAGID IncludeTag
= SdbFindFirstTag(pdb
, InExcludeTag
, TAG_INCLUDE
);
808 ModuleName
= SdbGetStringTagPtr(pdb
, ModuleTag
);
811 SeiAppendInExclude(dest
, ModuleName
, IncludeTag
!= TAGID_NULL
);
815 SHIMENG_WARN("INEXCLUDE without Module: 0x%x\n", InExcludeTag
);
818 InExcludeTag
= SdbFindNextTag(pdb
, parent
, InExcludeTag
);
822 VOID
SeiBuildGlobalInclExclList(HSDB hsdb
)
825 TAGREF tr
= TAGREF_ROOT
;
826 TAGID root
, db
, library
;
828 if (!SdbTagRefToTagID(hsdb
, tr
, &pdb
, &root
))
830 SHIMENG_WARN("Unable to resolve database root\n");
833 db
= SdbFindFirstTag(pdb
, root
, TAG_DATABASE
);
834 if (db
== TAGID_NULL
)
836 SHIMENG_WARN("Unable to resolve database\n");
839 library
= SdbFindFirstTag(pdb
, db
, TAG_LIBRARY
);
840 if (library
== TAGID_NULL
)
842 SHIMENG_WARN("Unable to resolve library\n");
846 SeiReadInExclude(pdb
, library
, &g_InExclude
);
849 VOID
SeiBuildInclExclList(PDB pdb
, TAGID ShimTag
, PSHIMINFO pShimInfo
)
853 /* First duplicate the global in/excludes */
854 for (n
= 0; n
< ARRAY_Size(&g_InExclude
); ++n
)
856 PINEXCLUDE InEx
= ARRAY_At(&g_InExclude
, INEXCLUDE
, n
);
857 SeiAppendInExclude(&pShimInfo
->InExclude
, InEx
->Module
.Buffer
, InEx
->Include
);
860 /* Now read this shim's in/excludes (possibly overriding the global ones) */
861 SeiReadInExclude(pdb
, ShimTag
, &pShimInfo
->InExclude
);
864 /* Given one loaded module, redirect (hook) all functions from the iat that are registered by shims */
865 VOID
SeiHookImports(PLDR_DATA_TABLE_ENTRY LdrEntry
)
868 PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor
;
869 PBYTE DllBase
= LdrEntry
->DllBase
;
871 if (SE_IsShimDll(DllBase
) || g_hInstance
== LdrEntry
->DllBase
)
873 SHIMENG_INFO("Skipping shim module 0x%p \"%wZ\"\n", LdrEntry
->DllBase
, &LdrEntry
->BaseDllName
);
877 if (LdrEntry
->Flags
& LDRP_COMPAT_DATABASE_PROCESSED
)
879 SHIMENG_INFO("Skipping module 0x%p \"%wZ\" because it was already processed\n", LdrEntry
->DllBase
, &LdrEntry
->BaseDllName
);
883 ImportDescriptor
= RtlImageDirectoryEntryToData(DllBase
, TRUE
, IMAGE_DIRECTORY_ENTRY_IMPORT
, &Size
);
884 if (!ImportDescriptor
)
886 SHIMENG_INFO("Skipping module 0x%p \"%wZ\" due to no iat found\n", LdrEntry
->DllBase
, &LdrEntry
->BaseDllName
);
890 SHIMENG_INFO("Hooking module 0x%p \"%wZ\"\n", LdrEntry
->DllBase
, &LdrEntry
->BaseDllName
);
892 for ( ;ImportDescriptor
->Name
&& ImportDescriptor
->OriginalFirstThunk
; ImportDescriptor
++)
894 PHOOKMODULEINFO HookModuleInfo
;
896 /* Do we have hooks for this module? */
897 HookModuleInfo
= SeiFindHookModuleInfoForImportDescriptor(DllBase
, ImportDescriptor
);
901 PIMAGE_THUNK_DATA OriginalThunk
, FirstThunk
;
904 for (n
= 0; n
< ARRAY_Size(&HookModuleInfo
->HookApis
); ++n
)
907 PHOOKAPIEX HookApi
= *ARRAY_At(&HookModuleInfo
->HookApis
, PHOOKAPIEX
, n
);
909 /* Check if this module should be excluded from being hooked (system32/winsxs, global or shim exclude) */
910 if (SeiIsExcluded(LdrEntry
, HookApi
))
915 OriginalThunk
= (PIMAGE_THUNK_DATA
)(DllBase
+ ImportDescriptor
->OriginalFirstThunk
);
916 FirstThunk
= (PIMAGE_THUNK_DATA
)(DllBase
+ ImportDescriptor
->FirstThunk
);
918 /* Walk all imports */
919 for (;OriginalThunk
->u1
.AddressOfData
&& FirstThunk
->u1
.Function
; OriginalThunk
++, FirstThunk
++)
921 if (!IMAGE_SNAP_BY_ORDINAL32(OriginalThunk
->u1
.AddressOfData
))
923 PIMAGE_IMPORT_BY_NAME ImportName
;
925 ImportName
= (PIMAGE_IMPORT_BY_NAME
)(DllBase
+ OriginalThunk
->u1
.AddressOfData
);
926 if (!strcmp((PCSTR
)ImportName
->Name
, HookApi
->FunctionName
))
928 SeiPatchNewImport(FirstThunk
, HookApi
, LdrEntry
);
930 /* Sadly, iat does not have to be sorted, and can even contain duplicate entries. */
936 SHIMENG_FAIL("Ordinals not yet supported\n");
943 /* One entry not found. */
945 SHIMENG_INFO("Entry \"%s!%s\" not found for \"%wZ\"\n", HookApi
->LibraryName
, HookApi
->FunctionName
, &LdrEntry
->BaseDllName
);
947 SHIMENG_INFO("Entry \"%s!%s\" found %d times for \"%wZ\"\n", HookApi
->LibraryName
, HookApi
->FunctionName
, dwFound
, &LdrEntry
->BaseDllName
);
953 /* Mark this module as processed. */
954 LdrEntry
->Flags
|= LDRP_COMPAT_DATABASE_PROCESSED
;
958 VOID
PatchNewModules(PPEB Peb
)
960 PLIST_ENTRY ListHead
, ListEntry
;
961 PLDR_DATA_TABLE_ENTRY LdrEntry
;
963 ListHead
= &NtCurrentPeb()->Ldr
->InLoadOrderModuleList
;
964 ListEntry
= ListHead
->Flink
;
966 while (ListHead
!= ListEntry
)
968 LdrEntry
= CONTAINING_RECORD(ListEntry
, LDR_DATA_TABLE_ENTRY
, InLoadOrderLinks
);
969 SeiHookImports(LdrEntry
);
971 ListEntry
= ListEntry
->Flink
;
976 VOID
SeiInitPaths(VOID
)
978 #define SYSTEM32 L"\\system32"
979 #define WINSXS L"\\winsxs"
981 PWSTR WindowsDirectory
= SdbpStrDup(SharedUserData
->NtSystemRoot
);
982 RtlInitUnicodeString(&g_WindowsDirectory
, WindowsDirectory
);
984 g_System32Directory
.MaximumLength
= g_WindowsDirectory
.Length
+ SdbpStrsize(SYSTEM32
);
985 g_System32Directory
.Buffer
= SdbpAlloc(g_System32Directory
.MaximumLength
);
986 RtlCopyUnicodeString(&g_System32Directory
, &g_WindowsDirectory
);
987 RtlAppendUnicodeToString(&g_System32Directory
, SYSTEM32
);
989 g_SxsDirectory
.MaximumLength
= g_WindowsDirectory
.Length
+ SdbpStrsize(WINSXS
);
990 g_SxsDirectory
.Buffer
= SdbpAlloc(g_SxsDirectory
.MaximumLength
);
991 RtlCopyUnicodeString(&g_SxsDirectory
, &g_WindowsDirectory
);
992 RtlAppendUnicodeToString(&g_SxsDirectory
, WINSXS
);
998 VOID
SeiInit(PUNICODE_STRING ProcessImage
, HSDB hsdb
, SDBQUERYRESULT
* pQuery
)
1002 DWORD dwTotalHooks
= 0;
1005 PPEB Peb
= NtCurrentPeb();
1007 /* We should only be called once! */
1008 ASSERT(g_pShimInfo
.ItemSize__
== 0);
1010 ARRAY_Init(&ShimRefArray
, TAGREF
);
1011 ARRAY_Init(&g_pShimInfo
, PSHIMMODULE
);
1012 ARRAY_Init(&g_pHookArray
, HOOKMODULEINFO
);
1013 ARRAY_Init(&g_InExclude
, INEXCLUDE
);
1014 RtlZeroMemory(&ShimFlags
, sizeof(ShimFlags
));
1018 SeiCheckComPlusImage(Peb
->ImageBaseAddress
);
1021 if (pQuery->trApphelp)
1022 SeiDisplayAppHelp(?pQuery->trApphelp?);
1025 SeiDbgPrint(SEI_MSG
, NULL
, "ShimInfo(ExePath(%wZ))\n", ProcessImage
);
1026 SeiBuildShimRefArray(hsdb
, pQuery
, &ShimRefArray
, &ShimFlags
);
1027 if (ShimFlags
.AppCompatFlags
.QuadPart
)
1029 SeiDbgPrint(SEI_MSG
, NULL
, "Using KERNEL apphack flags 0x%I64x\n", ShimFlags
.AppCompatFlags
.QuadPart
);
1030 Peb
->AppCompatFlags
.QuadPart
|= ShimFlags
.AppCompatFlags
.QuadPart
;
1032 if (ShimFlags
.AppCompatFlagsUser
.QuadPart
)
1034 SeiDbgPrint(SEI_MSG
, NULL
, "Using USER apphack flags 0x%I64x\n", ShimFlags
.AppCompatFlagsUser
.QuadPart
);
1035 Peb
->AppCompatFlagsUser
.QuadPart
|= ShimFlags
.AppCompatFlagsUser
.QuadPart
;
1037 if (ShimFlags
.ProcessParameters_Flags
)
1039 SeiDbgPrint(SEI_MSG
, NULL
, "Using ProcessParameters flags 0x%x\n", ShimFlags
.ProcessParameters_Flags
);
1040 Peb
->ProcessParameters
->Flags
|= ShimFlags
.ProcessParameters_Flags
;
1042 SeiDbgPrint(SEI_MSG
, NULL
, "ShimInfo(Complete)\n");
1044 SHIMENG_INFO("Got %d shims\n", ARRAY_Size(&ShimRefArray
));
1045 SeiBuildGlobalInclExclList(hsdb
);
1047 /* Walk all shims referenced (in layers + exes), and load their modules */
1048 for (n
= 0; n
< ARRAY_Size(&ShimRefArray
); ++n
)
1053 TAGREF tr
= *ARRAY_At(&ShimRefArray
, TAGREF
, n
);
1055 if (SdbTagRefToTagID(hsdb
, tr
, &pdb
, &ShimRef
))
1057 LPCWSTR ShimName
, DllName
, CommandLine
= NULL
;
1059 WCHAR FullNameBuffer
[MAX_PATH
];
1060 UNICODE_STRING UnicodeDllName
;
1062 PSHIMMODULE pShimModuleInfo
= NULL
;
1063 ANSI_STRING AnsiCommandLine
= RTL_CONSTANT_STRING("");
1064 PSHIMINFO pShimInfo
= NULL
;
1065 PHOOKAPIEX pHookApi
;
1068 ShimName
= SeiGetStringPtr(pdb
, ShimRef
, TAG_NAME
);
1071 SHIMENG_FAIL("Failed to retrieve the name for 0x%x\n", tr
);
1075 CommandLine
= SeiGetStringPtr(pdb
, ShimRef
, TAG_COMMAND_LINE
);
1076 if (CommandLine
&& *CommandLine
)
1078 RtlInitUnicodeString(&UnicodeDllName
, CommandLine
);
1079 if (NT_SUCCESS(RtlUnicodeStringToAnsiString(&AnsiCommandLine
, &UnicodeDllName
, TRUE
)))
1081 SHIMENG_INFO("COMMAND LINE %s for %S", AnsiCommandLine
.Buffer
, ShimName
);
1085 AnsiCommandLine
.Buffer
= "";
1090 ShimTag
= SeiGetDWORD(pdb
, ShimRef
, TAG_SHIM_TAGID
);
1093 SHIMENG_FAIL("Failed to resolve %S to a shim\n", ShimName
);
1097 if (!SUCCEEDED(SdbGetAppPatchDir(NULL
, FullNameBuffer
, ARRAYSIZE(FullNameBuffer
))))
1099 SHIMENG_WARN("Failed to get the AppPatch dir\n");
1103 DllName
= SeiGetStringPtr(pdb
, ShimTag
, TAG_DLLFILE
);
1104 if (DllName
== NULL
||
1105 !SUCCEEDED(StringCchCatW(FullNameBuffer
, ARRAYSIZE(FullNameBuffer
), L
"\\")) ||
1106 !SUCCEEDED(StringCchCatW(FullNameBuffer
, ARRAYSIZE(FullNameBuffer
), DllName
)))
1108 SHIMENG_WARN("Failed to build a full path for %S\n", ShimName
);
1112 RtlInitUnicodeString(&UnicodeDllName
, FullNameBuffer
);
1113 if (NT_SUCCESS(LdrGetDllHandle(NULL
, NULL
, &UnicodeDllName
, &BaseAddress
)))
1115 /* This shim dll was already loaded, let's find it */
1116 pShimModuleInfo
= SeiGetShimModuleInfo(BaseAddress
);
1118 else if (!NT_SUCCESS(LdrLoadDll(NULL
, NULL
, &UnicodeDllName
, &BaseAddress
)))
1120 SHIMENG_WARN("Failed to load %wZ for %S\n", &UnicodeDllName
, ShimName
);
1123 /* No shim module found (or we just loaded it) */
1124 if (!pShimModuleInfo
)
1126 pShimModuleInfo
= SeiCreateShimModuleInfo(DllName
, BaseAddress
);
1127 if (!pShimModuleInfo
)
1129 SHIMENG_FAIL("Failed to allocate ShimInfo for %S\n", DllName
);
1134 SHIMENG_INFO("Shim DLL 0x%p \"%wZ\" loaded\n", BaseAddress
, &UnicodeDllName
);
1135 SHIMENG_INFO("Using SHIM \"%S!%S\"\n", DllName
, ShimName
);
1137 /* Ask this shim what hooks it needs (and pass along the commandline) */
1138 pHookApi
= pShimModuleInfo
->pGetHookAPIs(AnsiCommandLine
.Buffer
, ShimName
, &dwHookCount
);
1139 SHIMENG_INFO("GetHookAPIs returns %d hooks for DLL \"%wZ\" SHIM \"%S\"\n", dwHookCount
, &UnicodeDllName
, ShimName
);
1141 pShimInfo
= SeiAppendHookInfo(pShimModuleInfo
, pHookApi
, dwHookCount
, ShimName
);
1143 /* If this shim has hooks, create the include / exclude lists */
1145 SeiBuildInclExclList(pdb
, ShimTag
, pShimInfo
);
1147 if (CommandLine
&& *CommandLine
)
1148 RtlFreeAnsiString(&AnsiCommandLine
);
1150 dwTotalHooks
+= dwHookCount
;
1154 SeiAddInternalHooks(dwTotalHooks
);
1156 PatchNewModules(Peb
);
1160 /* Load the database + unpack the shim data (if this process is allowed) */
1161 BOOL
SeiGetShimData(PUNICODE_STRING ProcessImage
, PVOID pShimData
, HSDB
* pHsdb
, SDBQUERYRESULT
* pQuery
)
1163 static const UNICODE_STRING ForbiddenShimmingApps
[] = {
1164 RTL_CONSTANT_STRING(L
"ntsd.exe"),
1165 RTL_CONSTANT_STRING(L
"windbg.exe"),
1167 RTL_CONSTANT_STRING(L
"slsvc.exe"),
1170 static const UNICODE_STRING BackSlash
= RTL_CONSTANT_STRING(L
"\\");
1171 static const UNICODE_STRING ForwdSlash
= RTL_CONSTANT_STRING(L
"/");
1172 UNICODE_STRING ProcessName
;
1173 USHORT Back
, Forward
;
1177 if (!NT_SUCCESS(RtlFindCharInUnicodeString(RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END
, ProcessImage
, &BackSlash
, &Back
)))
1180 if (!NT_SUCCESS(RtlFindCharInUnicodeString(RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END
, ProcessImage
, &ForwdSlash
, &Forward
)))
1187 Back
+= sizeof(WCHAR
);
1189 ProcessName
.Buffer
= ProcessImage
->Buffer
+ Back
/ sizeof(WCHAR
);
1190 ProcessName
.Length
= ProcessImage
->Length
- Back
;
1191 ProcessName
.MaximumLength
= ProcessImage
->MaximumLength
- Back
;
1193 for (n
= 0; n
< ARRAYSIZE(ForbiddenShimmingApps
); ++n
)
1195 if (RtlEqualUnicodeString(&ProcessName
, ForbiddenShimmingApps
+ n
, TRUE
))
1197 SHIMENG_MSG("Not shimming %wZ\n", ForbiddenShimmingApps
+ n
);
1202 /* We should probably load all db's here, but since we do not support that yet... */
1203 hsdb
= SdbInitDatabase(HID_DOS_PATHS
| SDB_DATABASE_MAIN_SHIM
, NULL
);
1206 if (SdbUnpackAppCompatData(hsdb
, ProcessImage
->Buffer
, pShimData
, pQuery
))
1211 SdbReleaseDatabase(hsdb
);
1218 VOID NTAPI
SE_InstallBeforeInit(PUNICODE_STRING ProcessImage
, PVOID pShimData
)
1221 SDBQUERYRESULT QueryResult
= { { 0 } };
1222 SHIMENG_INFO("(%wZ, %p)\n", ProcessImage
, pShimData
);
1224 if (!SeiGetShimData(ProcessImage
, pShimData
, &hsdb
, &QueryResult
))
1226 SHIMENG_FAIL("Failed to get shim data\n");
1230 g_bShimDuringInit
= TRUE
;
1231 SeiInit(ProcessImage
, hsdb
, &QueryResult
);
1232 g_bShimDuringInit
= FALSE
;
1234 SdbReleaseDatabase(hsdb
);
1237 VOID NTAPI
SE_InstallAfterInit(PUNICODE_STRING ProcessImage
, PVOID pShimData
)
1239 NotifyShims(SHIM_NOTIFY_ATTACH
, NULL
);
1242 VOID NTAPI
SE_ProcessDying(VOID
)
1244 SHIMENG_MSG("()\n");
1245 NotifyShims(SHIM_NOTIFY_DETACH
, NULL
);
1248 VOID WINAPI
SE_DllLoaded(PLDR_DATA_TABLE_ENTRY LdrEntry
)
1250 SHIMENG_INFO("%sINIT. loading DLL \"%wZ\"\n", g_bShimDuringInit
? "" : "AFTER ", &LdrEntry
->BaseDllName
);
1252 SeiHookImports(LdrEntry
);
1254 NotifyShims(SHIM_REASON_DLL_LOAD
, LdrEntry
);
1257 VOID WINAPI
SE_DllUnloaded(PLDR_DATA_TABLE_ENTRY LdrEntry
)
1259 SHIMENG_INFO("(%p)\n", LdrEntry
);
1261 /* Should we unhook here? */
1263 NotifyShims(SHIM_REASON_DLL_UNLOAD
, LdrEntry
);
1266 BOOL WINAPI
SE_IsShimDll(PVOID BaseAddress
)
1268 SHIMENG_INFO("(%p)\n", BaseAddress
);
1270 return SeiGetShimModuleInfo(BaseAddress
) != NULL
;