windows-nt/Source/XPSP1/NT/windows/appcompat/shimengines/engiat/shimeng.c

4939 lines
134 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1989-2000 Microsoft Corporation
Module Name:
ShimEng.c
Abstract:
This module implements the shim hooking using IAT thunking. The file
is shared between the Windows2000 and Whistler implementations.
Author:
clupu created 11 July 2000
Revision History:
clupu updated 12 Dec 2000 - one file for both Win2k and Whistler
--*/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <string.h>
#include <windef.h>
#include <winbase.h>
#include <stdio.h>
#include <apcompat.h>
#include "shimdb.h"
#include "ShimEng.h"
#ifdef SE_WIN2K
#include "NotifyCallback.h"
#endif // SE_WIN2K
#ifndef SE_WIN2K
extern BOOL
LdrInitShimEngineDynamic(
PVOID pShimengModule
);
#endif
BOOL g_bDbgPrintEnabled;
DEBUGLEVEL g_DebugLevel;
//
// The number of SHIMs that can be added dynamically by calling SE_DynamicShim
//
#define MAX_DYNAMIC_SHIMS 128
//
// Flags used in HOOKAPI.dwFlags
//
#define HAF_CHAINED 0x00000004
#define HAF_BOTTOM_OF_CHAIN 0x00000008
//
// Flags used in SHIMINFO.dwFlags
//
#define SIF_RESOLVED 0x00000001
//
// Pipe name for sending messages
//
#define PIPE_NAME L"\\Device\\NamedPipe\\ShimViewer"
typedef struct tagINEXMOD {
char* pszModule;
struct tagINEXMOD* pNext;
} INEXMOD, *PINEXMOD;
typedef enum tagINEX_MODE {
INEX_UNINITIALIZED = 0,
EXCLUDE_SYSTEM32,
EXCLUDE_ALL,
INCLUDE_ALL
} INEX_MODE, *PINEX_MODE;
#define MAX_SHIM_NAME_LEN 63
typedef struct tagSHIMINFO {
DWORD dwHookedAPIs; // the number of APIs hooked by this shim DLL
PVOID pDllBase; // the base address for this shim DLL
DWORD dwFlags; // internal flags
PINEXMOD pFirstInclude; // local inclusion/exclusion list
PINEXMOD pFirstExclude; // local inclusion/exclusion list
INEX_MODE eInExMode; // what inclusion mode are we in?
PLDR_DATA_TABLE_ENTRY pLdrEntry; // pointer to the loader entry for this
// shim DLL.
WCHAR wszName[MAX_SHIM_NAME_LEN + 1]; // name of shim
} SHIMINFO, *PSHIMINFO;
typedef struct tagHOOKEDMODULE {
PVOID pDllBase; // the base address of the loaded module
ULONG ulSizeOfImage; // the size of the DLL image
char szModuleName[128]; // the name of the loaded module
BOOL bInSystem32; // tru if the DLL hooked is in system32
} HOOKEDMODULE, *PHOOKEDMODULE;
//
// The prototypes of the internal stubs.
//
typedef PVOID (*PFNGETPROCADDRESS)(HMODULE hMod, char* pszProc);
typedef HINSTANCE (*PFNLOADLIBRARYA)(LPCSTR lpLibFileName);
typedef HINSTANCE (*PFNLOADLIBRARYW)(LPCWSTR lpLibFileName);
typedef HINSTANCE (*PFNLOADLIBRARYEXA)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
typedef HINSTANCE (*PFNLOADLIBRARYEXW)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
typedef BOOL (*PFNFREELIBRARY)(HMODULE hLibModule);
BOOL
SeiSendDataToPipe(
void
);
#ifdef SE_WIN2K
BOOL PatchNewModules(
BOOL bDynamic
);
#else
BOOL
SeiDisplayAppHelp(
HSDB hSDB,
PSDBQUERYRESULT pSdbQuery
);
#endif // SE_WIN2K
//
// Global function hooks the shim uses to keep from recursing itself
//
PFNRTLALLOCATEHEAP g_pfnRtlAllocateHeap;
PFNRTLFREEHEAP g_pfnRtlFreeHeap;
// Shim's private heap
PVOID g_pShimHeap;
// The global inclusion list.
PINEXMOD g_pGlobalInclusionList = NULL;
// Array with all the HOOKAPI list for all the shim DLLs
PHOOKAPI* g_pHookArray = NULL;
// this variable will only be valid on dynamic cases
HMODULE g_hModule = NULL;
// Internal HOOKAPI for the stubs that the shim engine provides
#define IHA_GetProcAddress 0
#ifdef SE_WIN2K
#define IHA_LoadLibraryA 1
#define IHA_LoadLibraryW 2
#define IHA_LoadLibraryExA 3
#define IHA_LoadLibraryExW 4
#define IHA_FreeLibrary 5
#define IHA_COUNT 6
#else
#define IHA_COUNT 1
#endif // SE_WIN2K
HOOKAPI g_IntHookAPI[IHA_COUNT];
// BUGBUG: comment here
HOOKAPIEX g_IntHookEx[IHA_COUNT];
// Extra info for all the shim DLLs
PSHIMINFO g_pShimInfo;
// The number of all shims applied to this process
DWORD g_dwShimsCount = 0;
// The maximum number of shims that can be applied.
DWORD g_dwMaxShimsCount = 0;
#define SHIM_MAX_HOOKED_MODULES 512
// The array of hooked modules
HOOKEDMODULE g_hHookedModules[SHIM_MAX_HOOKED_MODULES];
// The number of modules hooked
DWORD g_dwHookedModuleCount;
// True if the statically linked modules have been hooked
BOOL g_bShimInitialized = FALSE;
// Don will comment here
BOOL g_bShimDuringInit = FALSE;
#define SHIM_MAX_PATCH_COUNT 64
// The array of in memory patches
PBYTE g_pMemoryPatches[SHIM_MAX_PATCH_COUNT];
// The number of in memory patches
DWORD g_dwMemoryPatchCount;
// This shim engine's module handle
PVOID g_pShimEngModHandle;
// The system32 directory
WCHAR g_szSystem32[MAX_PATH] = L"";
// The length of the System32 directory string;
DWORD g_dwSystem32StrLen = 0;
// The SxS directory
WCHAR g_szSxS[MAX_PATH] = L"";
// The length of the SxS directory string;
DWORD g_dwSxSStrLen = 0;
// The windows directory
WCHAR g_szWindir[MAX_PATH] = L"";
// The exe name for sending data to our named pipe
WCHAR g_szExeName[MAX_PATH] = L"";
// The length of the windows directory string;
DWORD g_dwWindirStrLen = 0;
// Cmd.exe full path
WCHAR g_szCmdExePath[MAX_PATH];
// Are we using an exe entry to get shims from?
BOOL g_bUsingExe;
// Are we using a layer entry to get shims from?
BOOL g_bUsingLayer;
PLDR_DATA_TABLE_ENTRY g_pShimEngLdrEntry;
// This boolean tells if some global vars have been initialized.
BOOL g_bInitGlobals;
// This boolean tells if we shimmed the internal hooks.
BOOL g_bInternalHooksUsed;
// Pipe data
WCHAR g_szPipeData[1024];
#ifndef SE_WIN2K
// The name of the shim DLL that the engine is just about to load.
WCHAR g_wszShimDllInLoading[128];
PVOID g_hApphelpDllHelper;
UNICODE_STRING Kernel32String = RTL_CONSTANT_STRING(L"kernel32.dll");
UNICODE_STRING NtdllString = RTL_CONSTANT_STRING(L"ntdll.dll");
UNICODE_STRING VerifierdllString = RTL_CONSTANT_STRING(L"verifier.dll");
// This tells if the image shimmed is a COM+ image.
BOOL g_bComPlusImage;
#endif // SE_WIN2K
#ifdef DEBUG_SPEW
void
__cdecl
DebugPrintfEx(
DEBUGLEVEL dwDetail,
LPSTR pszFmt,
...
)
/*++
Return: void
Desc: This function prints debug spew in the debugger.
--*/
{
char szT[1024];
va_list arglist;
int len;
va_start(arglist, pszFmt);
_vsnprintf(szT, 1023, pszFmt, arglist);
szT[1022] = 0;
va_end(arglist);
//
// Make sure we have a '\n' at the end of the string
//
len = strlen(szT);
if (szT[len - 1] != '\n') {
strcpy(szT + len, "\n");
}
if (dwDetail <= g_DebugLevel) {
switch (dwDetail) {
case dlPrint:
DbgPrint("[MSG ] ");
break;
case dlError:
DbgPrint("[FAIL] ");
break;
case dlWarning:
DbgPrint("[WARN] ");
break;
case dlInfo:
DbgPrint("[INFO] ");
break;
}
DbgPrint("%s", szT);
}
}
void
SeiInitDebugSupport(
void
)
/*++
Return: void
Desc: This function initializes g_bDbgPrintEnabled based on an env variable.
--*/
{
NTSTATUS status;
UNICODE_STRING EnvName;
UNICODE_STRING EnvValue;
WCHAR wszEnvValue[128];
RtlInitUnicodeString(&EnvName, L"SHIMENG_DEBUG_LEVEL");
EnvValue.Buffer = wszEnvValue;
EnvValue.Length = 0;
EnvValue.MaximumLength = sizeof(wszEnvValue);
status = RtlQueryEnvironmentVariable_U(NULL, &EnvName, &EnvValue);
if (NT_SUCCESS(status)) {
WCHAR c = EnvValue.Buffer[0];
g_bDbgPrintEnabled = TRUE;
switch (c) {
case L'0':
g_DebugLevel = dlNone;
g_bDbgPrintEnabled = FALSE;
break;
case L'1':
g_DebugLevel = dlPrint;
break;
case L'2':
g_DebugLevel = dlError;
break;
case L'3':
g_DebugLevel = dlWarning;
break;
case L'4':
default:
g_DebugLevel = dlInfo;
break;
}
}
}
#endif // DEBUG_SPEW
PHOOKAPI
SeiConstructChain(
IN PVOID pfnOld, // Original API function pointer to resolve.
OUT DWORD* pdwDllIndex // Will receive the index of the shim DLL
// that provides the returning PHOOKAPI.
)
/*++
Return: Top-of-chain PHOOKAPI structure.
Desc: Scans HOOKAPI arrays for pfnOld and either constructs the
chain or returns the top-of-chain PHOOKAPI if the chain
already exists.
--*/
{
LONG i; // use LONG because we decrement this and compare it for positive
DWORD j;
PHOOKAPI pTopHookAPI = NULL;
PHOOKAPI pBottomHookAPI = NULL;
*pdwDllIndex = 0;
//
// Scan all HOOKAPI entries for corresponding function pointer.
//
for (i = (LONG)g_dwShimsCount - 1; i >= 0; i--) {
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
if (g_pHookArray[i][j].pfnOld == pfnOld) {
if (pTopHookAPI != NULL) {
//
// The chain has already begun, so tack this one on
// to the end.
//
pBottomHookAPI->pfnOld = g_pHookArray[i][j].pfnNew;
if (pBottomHookAPI->pHookEx) {
pBottomHookAPI->pHookEx->pNext = &(g_pHookArray[i][j]);
}
pBottomHookAPI = &(g_pHookArray[i][j]);
if (pBottomHookAPI->pHookEx) {
pBottomHookAPI->pHookEx->pTopOfChain = pTopHookAPI;
}
pBottomHookAPI->dwFlags |= HAF_CHAINED;
DPF(dlInfo, " 0x%p ->", pBottomHookAPI->pfnNew);
} else {
//
// This is the top of the chain. The inclusion/exclusion list
// from the DLL at the top of the chain is used to determine if
// an import entry in a particular DLL is hooked or not.
// See SeiHookImports for use of pdwIndex.
//
*pdwDllIndex = i;
if (g_pHookArray[i][j].pHookEx && g_pHookArray[i][j].pHookEx->pTopOfChain) {
//
// Chain has already been constructed.
//
return g_pHookArray[i][j].pHookEx->pTopOfChain;
}
//
// Not hooked yet. Set to top of chain.
//
pTopHookAPI = &(g_pHookArray[i][j]);
if (pTopHookAPI->pHookEx) {
pTopHookAPI->pHookEx->pTopOfChain = pTopHookAPI;
}
pTopHookAPI->dwFlags |= HAF_CHAINED;
pBottomHookAPI = pTopHookAPI;
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo, "[SeiConstructChain] %s!#%d 0x%p ->",
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName,
pTopHookAPI->pfnNew);
} else {
DPF(dlInfo, "[SeiConstructChain] %s!%-20s 0x%p ->",
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName,
pTopHookAPI->pfnNew);
}
}
}
}
}
if (pBottomHookAPI != NULL) {
pBottomHookAPI->dwFlags |= HAF_BOTTOM_OF_CHAIN;
DPF(dlInfo, " 0x%p\n", pBottomHookAPI->pfnOld);
}
return pTopHookAPI;
}
PVOID
SeiGetPatchAddress(
IN PRELATIVE_MODULE_ADDRESS pRelAddress // A RELATIVE_MODULE_ADDRESS structure
// that defines the memory location as
// an offset from a loaded module.
)
/*++
Return: The actual memory address of the specified module + offset.
Desc: Resolves a RELATIVE_MODULE_ADDRESS structure into an actual memory address.
--*/
{
WCHAR wszModule[MAX_PATH];
PVOID ModuleHandle = NULL;
UNICODE_STRING UnicodeString;
NTSTATUS status;
PPEB Peb = NtCurrentPeb();
if (pRelAddress->moduleName[0] != 0) {
//
// Copy the module name from the patch since it will typically be misaligned.
//
wcscpy(wszModule, pRelAddress->moduleName);
RtlInitUnicodeString(&UnicodeString, wszModule);
//
// Make sure the module is loaded before calculating address ranges.
//
status = LdrGetDllHandle(NULL, NULL, &UnicodeString, &ModuleHandle);
if (!NT_SUCCESS(status)) {
DPF(dlWarning,
"[SeiGetPatchAddress] Dll \"%S\" not yet loaded for memory patching.\n",
wszModule);
return NULL;
}
//
// We're done, return the address
//
return (PVOID)((ULONG_PTR)ModuleHandle + (ULONG_PTR)pRelAddress->address);
}
//
// This patch is for the main EXE.
//
return (PVOID)((ULONG_PTR)Peb->ImageBaseAddress + (ULONG_PTR)pRelAddress->address);
}
int
SeiApplyPatch(
IN PBYTE pPatch // A patch code blob.
)
/*++
Return: 1 for success, 0 for failure.
Desc: Attempts to execute all commands in a patch code blob. If DLLs are not loaded,
this function will return 0.
--*/
{
PPATCHMATCHDATA pMatchData;
PPATCHWRITEDATA pWriteData;
PPATCHOP pPatchOP;
NTSTATUS status;
PVOID pAddress;
PVOID pProtectFuncAddress = NULL;
SIZE_T dwProtectSize = 0;
DWORD dwOldFlags = 0;
//
// Grab the opcode and see what we have to do.
//
while (TRUE) {
pPatchOP = (PPATCHOP)pPatch;
switch (pPatchOP->dwOpcode) {
case PEND:
return 1;
case PWD:
//
// This is a patch write data primitive - write the data.
//
pWriteData = (PPATCHWRITEDATA)pPatchOP->data;
//
// Grab the physical address to do this operation.
//
pAddress = SeiGetPatchAddress(&(pWriteData->rva));
if (pAddress == NULL) {
DPF(dlWarning, "[SeiApplyPatch] DLL not loaded for memory patching.\n");
return 0;
}
//
// Fixup the page attributes.
//
dwProtectSize = pWriteData->dwSizeData;
pProtectFuncAddress = pAddress;
status = NtProtectVirtualMemory(NtCurrentProcess(),
(PVOID)&pProtectFuncAddress,
&dwProtectSize,
PAGE_READWRITE,
&dwOldFlags);
if (!NT_SUCCESS(status)) {
DPF(dlError, "[SeiApplyPatch] NtProtectVirtualMemory failed 0x%X.\n",
status);
return 0;
}
//
// Copy the patch bytes.
//
RtlCopyMemory((PVOID)pAddress, (PVOID)pWriteData->data, pWriteData->dwSizeData);
//
// Restore the page protection.
//
dwProtectSize = pWriteData->dwSizeData;
pProtectFuncAddress = pAddress;
status = NtProtectVirtualMemory(NtCurrentProcess(),
(PVOID)&pProtectFuncAddress,
&dwProtectSize,
dwOldFlags,
&dwOldFlags);
if (!NT_SUCCESS(status)) {
DPF(dlError, "[SeiApplyPatch] NtProtectVirtualMemory failed 0x%X.\n",
status);
return 0;
}
status = NtFlushInstructionCache(NtCurrentProcess(),
pProtectFuncAddress,
dwProtectSize);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiApplyPatch] NtFlushInstructionCache failed w/ status 0x%X.\n",
status);
}
break;
case PMAT:
//
// This is a patch match data at offset primitive.
//
pMatchData = (PPATCHMATCHDATA)pPatchOP->data;
//
// Grab the physical address to do this operation
//
pAddress = SeiGetPatchAddress(&(pMatchData->rva));
if (pAddress == NULL) {
DPF(dlWarning, "[SeiApplyPatch] SeiGetPatchAddress failed.\n");
return 0;
}
//
// Make sure there is a match with what we expect to be there.
//
if (!RtlEqualMemory(pMatchData->data, (PBYTE)pAddress, pMatchData->dwSizeData)) {
DPF(dlError, "[SeiApplyPatch] Failure matching on patch data.\n");
return 0;
}
break;
default:
//
// If this happens we got an unexpected operation and we have to fail.
//
DPF(dlError, "[SeiApplyPatch] Unknown patch opcode 0x%X.\n",
pPatchOP->dwOpcode);
ASSERT(0);
return 0;
}
//
// Next opcode.
//
pPatch = (PBYTE)(pPatchOP->dwNextOpcode + pPatch);
}
return 1;
}
void
SeiAttemptPatches(
void
)
/*++
Return: void.
Desc: Attempts all patches in the global array.
--*/
{
DWORD i, dwSucceeded = 0;
for (i = 0; i < g_dwMemoryPatchCount; i++) {
dwSucceeded += SeiApplyPatch(g_pMemoryPatches[i]);
}
if (g_dwMemoryPatchCount > 0) {
DPF(dlInfo, "[SeiAttemptPatches] Applied %d of %d patches.\n",
dwSucceeded,
g_dwMemoryPatchCount);
swprintf(g_szPipeData, L"%s - Applied %d of %d patches",
g_szExeName,
dwSucceeded,
g_dwMemoryPatchCount);
SeiSendDataToPipe();
}
}
void
SeiResolveAPIs(
void
)
/*++
Return: void
Desc: Loops through the array of HOOKAPI and sets pfnOld if it wasn't
already set.
--*/
{
DWORD i, j;
ANSI_STRING AnsiString;
UNICODE_STRING UnicodeString;
WCHAR wszBuffer[MAX_PATH];
STRING ProcedureNameString;
PVOID pfnOld;
PVOID ModuleHandle = NULL;
NTSTATUS status;
BOOL bAllApisResolved;
char* pszFunctionName;
UnicodeString.Buffer = wszBuffer;
for (i = 0; i < g_dwShimsCount; i++) {
//
// See if we've already resolved all the APIs this shim DLL wanted to hook.
//
if (g_pShimInfo[i].dwFlags & SIF_RESOLVED) {
continue;
}
bAllApisResolved = TRUE;
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
//
// Ignore resolved APIs.
//
if (g_pHookArray[i][j].pfnOld != NULL) {
continue;
}
//
// Don't try to load unspecified modules.
//
if (g_pHookArray[i][j].pszModule == NULL) {
continue;
}
//
// Is this DLL mapped in the address space?
//
RtlInitAnsiString(&AnsiString, g_pHookArray[i][j].pszModule);
UnicodeString.MaximumLength = sizeof(wszBuffer);
if (!NT_SUCCESS(RtlAnsiStringToUnicodeString(&UnicodeString,
&AnsiString,
FALSE))){
DPF(dlError,
"[SeiResolveAPIs] Failed to convert string \"%s\" to UNICODE.\n",
g_pHookArray[i][j].pszModule);
continue;
}
status = LdrGetDllHandle(NULL,
NULL,
&UnicodeString,
&ModuleHandle);
if (!NT_SUCCESS(status)) {
bAllApisResolved = FALSE;
continue;
}
//
// Get the original entry point for this hook.
//
pszFunctionName = g_pHookArray[i][j].pszFunctionName;
if ((ULONG_PTR)pszFunctionName < 0x0000FFFF) {
status = LdrGetProcedureAddress(ModuleHandle,
NULL,
(ULONG)(ULONG_PTR)pszFunctionName,
&pfnOld);
} else {
RtlInitString(&ProcedureNameString, pszFunctionName);
status = LdrGetProcedureAddress(ModuleHandle,
&ProcedureNameString,
0,
&pfnOld);
}
if (!NT_SUCCESS(status) || pfnOld == NULL) {
bAllApisResolved = FALSE;
if ((ULONG_PTR)pszFunctionName < 0x0000FFFF) {
DPF(dlError, "[SeiResolveAPIs] There is no \"%s!#%d\" !\n",
g_pHookArray[i][j].pszModule,
pszFunctionName);
} else {
DPF(dlError, "[SeiResolveAPIs] There is no \"%s!%s\" !\n",
g_pHookArray[i][j].pszModule,
pszFunctionName);
}
continue;
}
g_pHookArray[i][j].pfnOld = pfnOld;
if ((ULONG_PTR)pszFunctionName < 0x0000FFFF) {
DPF(dlInfo, "[SeiResolveAPIs] Resolved \"%s!#%d\" to 0x%p\n",
g_pHookArray[i][j].pszModule,
pszFunctionName,
pfnOld);
} else {
DPF(dlInfo, "[SeiResolveAPIs] Resolved \"%s!%s\" to 0x%p\n",
g_pHookArray[i][j].pszModule,
pszFunctionName,
pfnOld);
}
}
//
// See if all the APIs were resolved for this shim DLL.
//
if (bAllApisResolved) {
g_pShimInfo[i].dwFlags |= SIF_RESOLVED;
}
}
}
BOOL
SeiIsExcluded(
IN LPCSTR pszModule, // The module to test for exclusion.
IN PHOOKAPI pTopHookAPI, // The HOOKAPI for which we test for exclusion.
IN BOOL bInSystem32 // Whether the module is located in the System32 directory.
)
/*++
Return: TRUE if the requested module shouldn't be patched.
Desc: Checks the inclusion/exclusion list of the shim DLL specified by
dwCounter and then checks the global exclusion list also.
--*/
{
BOOL bExclude = TRUE;
BOOL bShimWantsToExclude = FALSE; // was there a shim that wanted to exclude?
PHOOKAPI pHook = pTopHookAPI;
//
// The current process is to only exclude a chain if every shim in the chain wants to
// exclude. If one shim needs to be included, the whole chain is included.
//
while (pHook && pHook->pHookEx) {
DWORD dwCounter;
dwCounter = pHook->pHookEx->dwShimID;
switch (g_pShimInfo[dwCounter].eInExMode) {
case INCLUDE_ALL:
{
//
// We include everything except what's in the exclude list.
//
PINEXMOD pExcludeMod;
pExcludeMod = g_pShimInfo[dwCounter].pFirstExclude;
while (pExcludeMod != NULL) {
if (_stricmp(pExcludeMod->pszModule, pszModule) == 0) {
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo,
"[SeiIsExcluded] Module \"%s\" excluded for shim %S, API \"%s!#%d\", because it is in the exclude list (MODE: IA).\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
} else {
DPF(dlInfo,
"[SeiIsExcluded] Module \"%s\" excluded for shim %S, API \"%s!%s\", because it is in the exclude list (MODE: IA).\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
}
//
// this wants to be excluded, so we go to the next
// shim, and see if it wants to be included
//
bShimWantsToExclude = TRUE;
goto nextShim;
}
pExcludeMod = pExcludeMod->pNext;
}
//
// we should include this shim, and therefore, the whole chain
//
bExclude = FALSE;
goto out;
break;
}
case EXCLUDE_SYSTEM32:
{
//
// In this case, we first check the include list,
// then exclude it if it's in System32, then exclude it if
// it's in the exclude list.
//
PINEXMOD pIncludeMod;
PINEXMOD pExcludeMod;
pIncludeMod = g_pShimInfo[dwCounter].pFirstInclude;
pExcludeMod = g_pShimInfo[dwCounter].pFirstExclude;
//
// First, check the include list.
//
while (pIncludeMod != NULL) {
if (_stricmp(pIncludeMod->pszModule, pszModule) == 0) {
//
// we should include this shim, and therefore, the whole chain
//
bExclude = FALSE;
goto out;
}
pIncludeMod = pIncludeMod->pNext;
}
//
// it wasn't in the include list, so is it in System32?
//
if (bInSystem32) {
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo,
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!#%d\", because it is in System32.\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
} else {
DPF(dlInfo,
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!%s\", because it is in System32.\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
}
//
// this wants to be excluded, so we go to the next
// shim, and see if it wants to be included
//
bShimWantsToExclude = TRUE;
goto nextShim;
}
//
// it wasn't in System32, so is it in the exclude list?
//
while (pExcludeMod != NULL) {
if (_stricmp(pExcludeMod->pszModule, pszModule) == 0) {
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo,
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!#%d\", because it is in the exclude list (MODE: ES).\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
} else {
DPF(dlInfo,
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!%s\", because it is in the exclude list (MODE: ES).\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
}
//
// this wants to be excluded, so we go to the next
// shim, and see if it wants to be included
//
bShimWantsToExclude = TRUE;
goto nextShim;
}
pExcludeMod = pExcludeMod->pNext;
}
//
// we should include this shim, and therefore, the whole chain
//
bExclude = FALSE;
goto out;
break;
}
case EXCLUDE_ALL:
{
//
// We exclude everything except what is in the include list.
//
PINEXMOD pIncludeMod;
pIncludeMod = g_pShimInfo[dwCounter].pFirstInclude;
while (pIncludeMod != NULL) {
if (_stricmp(pIncludeMod->pszModule, pszModule) == 0) {
//
// we should include this shim, and therefore, the whole chain
//
bExclude = FALSE;
goto out;
}
pIncludeMod = pIncludeMod->pNext;
}
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo,
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!#%d\", because it is not in the include list (MODE: EA).\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
} else {
DPF(dlInfo,
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!%s\", because it is not in the include list (MODE: EA).\n",
pszModule,
g_pShimInfo[dwCounter].wszName,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
}
//
// this wants to be excluded, so we go to the next
// shim, and see if it wants to be included
//
bShimWantsToExclude = TRUE;
goto nextShim;
break;
}
}
nextShim:
pHook = pHook->pHookEx->pNext;
}
out:
if (!bExclude && bShimWantsToExclude) {
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlError,
"[SeiIsExcluded] Module \"%s\" mixed inclusion/exclusion for "
"API \"%s!#%d\". Included.\n",
pszModule,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
} else {
DPF(dlError,
"[SeiIsExcluded] Module \"%s\" mixed inclusion/exclusion for "
"API \"%s!%s\". Included.\n",
pszModule,
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName);
}
}
return bExclude;
}
BOOL
SeiHookImports(
IN PBYTE pDllBase, // The base address of the DLL to be hooked
IN ULONG ulSizeOfImage, // The size of the DLL image
IN PUNICODE_STRING pstrDllName, // The name of the DLL to be hooked
IN BOOL bInSystem32 // TRUE if this is a system32 DLL
)
/*++
Return: TRUE if successful.
Desc: Walks the import table of the specified module and patches the APIs
that need to be hooked.
--*/
{
CHAR szBaseDllName[MAX_PATH];
ANSI_STRING AnsiString = { 0, sizeof(szBaseDllName), szBaseDllName };
NTSTATUS status;
BOOL bAnyHooked = FALSE;
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pDllBase;
PIMAGE_NT_HEADERS pINTH;
PIMAGE_IMPORT_DESCRIPTOR pIID;
DWORD dwImportTableOffset;
PHOOKAPI pTopHookAPI;
DWORD dwOldProtect, dwOldProtect2;
SIZE_T dwProtectSize;
DWORD i, j;
PVOID pfnOld;
status = RtlUnicodeStringToAnsiString(&AnsiString, pstrDllName, FALSE);
if (!NT_SUCCESS(status)) {
DPF(dlError, "[SeiHookImports] Cannot convert \"%S\" to ANSI\n",
pstrDllName->Buffer);
return FALSE;
}
//
// Get the import table.
//
pINTH = (PIMAGE_NT_HEADERS)(pDllBase + pIDH->e_lfanew);
dwImportTableOffset = pINTH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (dwImportTableOffset == 0) {
//
// No import table found. This is probably ntdll.dll
//
return TRUE;
}
DPF(dlInfo, "[SeiHookImports] Hooking module 0x%p \"%s\"\n", pDllBase, szBaseDllName);
pIID = (PIMAGE_IMPORT_DESCRIPTOR)(pDllBase + dwImportTableOffset);
//
// Loop through the import table and search for the APIs that we want to patch
//
while (TRUE) {
LPSTR pszImportEntryModule;
PIMAGE_THUNK_DATA pITDA;
//
// Return if no first thunk (terminating condition).
//
if (pIID->FirstThunk == 0) {
break;
}
pszImportEntryModule = (LPSTR)(pDllBase + pIID->Name);
//
// If we're not interested in this module jump to the next.
//
bAnyHooked = FALSE;
for (i = 0; i < g_dwShimsCount; i++) {
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
if (g_pHookArray[i][j].pszModule != NULL &&
_stricmp(g_pHookArray[i][j].pszModule, pszImportEntryModule) == 0) {
bAnyHooked = TRUE;
goto ScanDone;
}
}
}
ScanDone:
if (!bAnyHooked) {
pIID++;
continue;
}
//
// We have APIs to hook for this module!
//
pITDA = (PIMAGE_THUNK_DATA)(pDllBase + (DWORD)pIID->FirstThunk);
while (TRUE) {
SIZE_T dwFuncAddr;
pfnOld = (PVOID)pITDA->u1.Function;
//
// Done with all the imports from this module? (terminating condition)
//
if (pITDA->u1.Ordinal == 0) {
break;
}
pTopHookAPI = SeiConstructChain(pfnOld, &i);
if (pTopHookAPI == NULL || SeiIsExcluded(szBaseDllName, pTopHookAPI, bInSystem32)) {
pITDA++;
continue;
}
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo,
"[SeiHookImports] Hooking API \"%s!#%d\" for DLL \"%s\"\n",
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName,
szBaseDllName);
} else {
DPF(dlInfo,
"[SeiHookImports] Hooking API \"%s!%s\" for DLL \"%s\"\n",
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName,
szBaseDllName);
}
//
// Make the code page writable and overwrite new function pointer
// in the import table.
//
dwProtectSize = sizeof(DWORD);
dwFuncAddr = (SIZE_T)&pITDA->u1.Function;
status = NtProtectVirtualMemory(NtCurrentProcess(),
(PVOID)&dwFuncAddr,
&dwProtectSize,
PAGE_READWRITE,
&dwOldProtect);
if (NT_SUCCESS(status)) {
pITDA->u1.Function = (SIZE_T)pTopHookAPI->pfnNew;
dwProtectSize = sizeof(DWORD);
status = NtProtectVirtualMemory(NtCurrentProcess(),
(PVOID)&dwFuncAddr,
&dwProtectSize,
dwOldProtect,
&dwOldProtect2);
if (!NT_SUCCESS(status)) {
DPF(dlError, "[SeiHookImports] Failed to change back the protection\n");
}
} else {
DPF(dlError,
"[SeiHookImports] Failed 0x%X to change protection to PAGE_READWRITE."
" Addr 0x%p\n",
status,
&pITDA->u1.Function);
}
pITDA++;
}
pIID++;
}
//
// Add the hooked module to the list of hooked modules
//
g_hHookedModules[g_dwHookedModuleCount].pDllBase = pDllBase;
g_hHookedModules[g_dwHookedModuleCount].ulSizeOfImage = ulSizeOfImage;
g_hHookedModules[g_dwHookedModuleCount].bInSystem32 = bInSystem32;
strcpy(g_hHookedModules[g_dwHookedModuleCount++].szModuleName, szBaseDllName);
return TRUE;
}
//
// NOTE: This used to be an exported function in the Win2k shim engine so
// let's not change its name.
//
BOOL
PatchNewModules(
BOOL bDynamic
)
/*++
Return: STATUS_SUCCESS if successful
Desc: Walks the loader list of loaded modules and attempts to patch all
the modules that are not already patched. It also attempts to
install the in memory patches.
--*/
{
PPEB Peb = NtCurrentPeb();
PLIST_ENTRY LdrHead;
PLIST_ENTRY LdrNext;
DWORD i;
BOOL bInSystem32;
//
// Resolve any APIs that became available from newly loaded modules.
//
SeiResolveAPIs();
if (g_bShimInitialized) {
DPF(dlInfo, "[PatchNewModules] Dynamic loaded modules\n");
}
//
// Try to apply memory patches.
//
SeiAttemptPatches();
//
// Return if only patches were required.
//
if (g_dwShimsCount == 0) {
return TRUE;
}
//
// Loop through the loaded modules
//
LdrHead = &Peb->Ldr->InMemoryOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
PLDR_DATA_TABLE_ENTRY LdrEntry;
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if ((SSIZE_T)LdrEntry->DllBase < 0) {
DPF(dlWarning, "[PatchNewModules] Not hooking kernel-mode DLL \"%S\"\n",
LdrEntry->BaseDllName.Buffer);
goto Continue;
}
//
// Don't hook our shim DLLs!
//
for (i = 0; i < g_dwShimsCount; i++) {
if (g_pShimInfo[i].pDllBase == LdrEntry->DllBase) {
goto Continue;
}
}
//
// Don't hook the shim engine!
//
if (LdrEntry->DllBase == g_pShimEngModHandle) {
goto Continue;
}
//
// Do nothing if it's already hooked.
//
if (!bDynamic) {
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (LdrEntry->DllBase == g_hHookedModules[i].pDllBase) {
goto Continue;
}
}
}
//
// Make sure we're not hooking more DLLs than we can.
//
if (g_dwHookedModuleCount == SHIM_MAX_HOOKED_MODULES - 1) {
DPF(dlError, "\n[PatchNewModules] Too many modules hooked!!!\n\n");
ASSERT(g_dwHookedModuleCount < SHIM_MAX_HOOKED_MODULES - 1);
break;
}
//
// Check if this DLL is in System32 (or WinSxS), and hence a possible candidate for blanket
// exclusion.
//
if ((g_dwSystem32StrLen && _wcsnicmp(g_szSystem32,
LdrEntry->FullDllName.Buffer, g_dwSystem32StrLen) == 0) ||
(g_dwSxSStrLen && _wcsnicmp(g_szSxS,
LdrEntry->FullDllName.Buffer, g_dwSxSStrLen) == 0)) {
bInSystem32 = TRUE;
} else {
bInSystem32 = FALSE;
}
//
// This is a candidate for hooking.
//
SeiHookImports(LdrEntry->DllBase,
LdrEntry->SizeOfImage,
&LdrEntry->BaseDllName,
bInSystem32);
Continue:
LdrNext = LdrEntry->InMemoryOrderLinks.Flink;
}
return TRUE;
}
void
SeiBuildGlobalInclList(
IN HSDB hSDB // the handle to the database channel
)
/*++
Return: void
Desc: This function builds the global inclusion list by reading it from the
database.
--*/
{
TAGREF trDatabase, trLibrary, trInExList, trModule;
WCHAR wszModule[MAX_PATH];
CHAR szModule[MAX_PATH];
ANSI_STRING AnsiString = { 0, sizeof(szModule), szModule };
UNICODE_STRING UnicodeString;
PINEXMOD pInExMod;
SIZE_T len;
NTSTATUS status;
//
// See if the list is not already built.
//
if (g_pGlobalInclusionList) {
return;
}
trDatabase = SdbFindFirstTagRef(hSDB, TAGID_ROOT, TAG_DATABASE);
if (trDatabase == TAGREF_NULL) {
DPF(dlError, "[SeiBuildGlobalInclList] Corrupt database. TAG_DATABASE\n");
ASSERT(trDatabase != TAGREF_NULL);
return;
}
trLibrary = SdbFindFirstTagRef(hSDB, trDatabase, TAG_LIBRARY);
if (trLibrary == TAGREF_NULL) {
DPF(dlError, "[SeiBuildGlobalInclList] Corrupt database. TAG_LIBRARY\n");
ASSERT(trLibrary != TAGREF_NULL);
return;
}
trInExList = SdbFindFirstTagRef(hSDB, trLibrary, TAG_INEXCLUDE);
if (trInExList == TAGREF_NULL) {
DPF(dlWarning, "[SeiBuildGlobalInclList] no global inclusion list.\n");
//
// This is not a problem. It just means there is no
// global inclusion list.
//
return;
}
if (trInExList != TAGREF_NULL) {
DPF(dlInfo, "[SeiBuildGlobalInclList] Global inclusion list:\n");
}
while (trInExList != TAGREF_NULL) {
trModule = SdbFindFirstTagRef(hSDB, trInExList, TAG_MODULE);
if (trModule == TAGREF_NULL) {
DPF(dlError,
"[SeiBuildGlobalInclList] Corrupt database. Global exclusion list w/o module\n");
ASSERT(trModule != TAGREF_NULL);
return;
}
if (!SdbReadStringTagRef(hSDB, trModule, wszModule, MAX_PATH * sizeof(WCHAR))) {
DPF(dlError,
"[SeiBuildGlobalInclList] Corrupt database. Inclusion list w/ bad module\n");
ASSERT(0);
return;
}
//
// Check for EXE name. The EXE should not be in the global inclusion list.
//
if (wszModule[0] == L'$') {
//
// The EXE name should not be specified in the global exclusion list.
//
DPF(dlError,
"[SeiBuildGlobalInclList] EXE name used in the global exclusion list!\n");
ASSERT(0);
goto Continue;
}
RtlInitUnicodeString(&UnicodeString, wszModule);
status = RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, FALSE);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiBuildGlobalInclList] 0x%X Cannot convert UNICODE \"%S\" to ANSI\n",
status, wszModule);
ASSERT(0);
return;
}
pInExMod = (PINEXMOD)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(INEXMOD));
if (pInExMod == NULL) {
DPF(dlError,
"[SeiBuildGlobalInclList] Failed to allocate %d bytes\n",
sizeof(INEXMOD));
return;
}
len = strlen(szModule) + 1;
pInExMod->pszModule = (char*)(*g_pfnRtlAllocateHeap)(g_pShimHeap, 0, len);
if (pInExMod->pszModule == NULL) {
DPF(dlError, "[SeiBuildGlobalInclList] Failed to allocate %d bytes\n", len);
return;
}
RtlCopyMemory(pInExMod->pszModule, szModule, len);
//
// Link it in the list.
//
pInExMod->pNext = g_pGlobalInclusionList;
g_pGlobalInclusionList = pInExMod;
DPF(dlInfo, "\t\"%s\"\n", pInExMod->pszModule);
Continue:
trInExList = SdbFindNextTagRef(hSDB, trLibrary, trInExList);
}
}
void
SeiEmptyInclExclList(
IN DWORD dwCounter
)
/*++
Return: void
Desc: This function empties the inclusion and exclusion lists for the specified
shim.
--*/
{
PINEXMOD pInExMod;
PINEXMOD pInExFree;
//
// First the include list.
//
pInExMod = g_pShimInfo[dwCounter].pFirstInclude;
while (pInExMod != NULL) {
pInExFree = pInExMod;
pInExMod = pInExMod->pNext;
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pInExFree->pszModule);
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pInExFree);
}
g_pShimInfo[dwCounter].pFirstInclude = NULL;
//
// Now the exclude list.
//
pInExMod = g_pShimInfo[dwCounter].pFirstExclude;
while (pInExMod != NULL) {
pInExFree = pInExMod;
pInExMod = pInExMod->pNext;
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pInExFree->pszModule);
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pInExFree);
}
g_pShimInfo[dwCounter].pFirstExclude = NULL;
}
#define MAX_LOCAL_INCLUDES 64 // max of 64 Incl/Excl statements
BOOL
SeiBuildInclExclListForShim(
IN HSDB hSDB, // handle to the database channel
IN TAGREF trShim, // TAGREF to the shim entry
IN DWORD dwCounter, // the index for the shim
IN LPCWSTR pwszExePath // full path to the EXE
)
/*++
Return: STATUS_SUCCESS on success, STATUS_UNSUCCESSFUL on failure.
Desc: This function builds the inclusion and exclusion lists for the
specified shim.
--*/
{
TAGREF trInExList, trModule, trInclude;
WCHAR wszModule[MAX_PATH];
CHAR szModule[MAX_PATH];
ANSI_STRING AnsiString = { 0, sizeof(szModule), szModule };
UNICODE_STRING UnicodeString;
PINEXMOD pInExMod;
SIZE_T len;
int nInEx;
BOOL bInclude;
DWORD trArrInEx[MAX_LOCAL_INCLUDES];
NTSTATUS status;
trInExList = SdbFindFirstTagRef(hSDB, trShim, TAG_INEXCLUDE);
nInEx = 0;
//
// Count the number of inclusion/exclusion statements. We need to do
// this first because the statements are written into the sdb file
// from bottom to top.
//
while (trInExList != TAGREF_NULL && nInEx < MAX_LOCAL_INCLUDES) {
trArrInEx[nInEx++] = trInExList;
trInExList = SdbFindNextTagRef(hSDB, trShim, trInExList);
ASSERT(nInEx <= MAX_LOCAL_INCLUDES);
}
if (nInEx == 0) {
return TRUE;
}
nInEx--;
while (nInEx >= 0) {
trInExList = trArrInEx[nInEx];
trInclude = SdbFindFirstTagRef(hSDB, trInExList, TAG_INCLUDE);
bInclude = (trInclude != TAGREF_NULL);
trModule = SdbFindFirstTagRef(hSDB, trInExList, TAG_MODULE);
if (trModule == TAGREF_NULL) {
DPF(dlError,
"[SeiBuildInclExclListForShim] Corrupt database. Incl/Excl list w/o module\n");
ASSERT(trModule != TAGREF_NULL);
return FALSE;
}
if (!SdbReadStringTagRef(hSDB, trModule, wszModule, MAX_PATH * sizeof(WCHAR))) {
DPF(dlError,
"[SeiBuildInclExclListForShim] Corrupt database. Incl/Excl list w/ bad module\n");
ASSERT(0);
return FALSE;
}
//
// Special case for '*'. '*' means all modules.
//
// NOTE: this option is ignored for dynamic shimming.
//
if (wszModule[0] == L'*') {
if (bInclude) {
//
// This is INCLUDE MODULE="*"
// Mark that we are in INCLUDE_ALL mode.
//
g_pShimInfo[dwCounter].eInExMode = INCLUDE_ALL;
} else {
//
// This is EXCLUDE MODULE="*"
// Mark that we are in EXCLUDE_ALL mode.
//
g_pShimInfo[dwCounter].eInExMode = EXCLUDE_ALL;
}
SeiEmptyInclExclList(dwCounter);
} else {
if (wszModule[0] == L'$') {
//
// Special case for EXE name. Get the name of the executable.
//
LPCWSTR pwszWalk = pwszExePath + wcslen(pwszExePath);
while (pwszWalk >= pwszExePath) {
if (*pwszWalk == '\\') {
break;
}
pwszWalk--;
}
wcscpy(wszModule, pwszWalk + 1);
DPF(dlInfo,
"[SeiBuildInclExclListForShim] EXE name resolved to \"%S\".\n",
wszModule);
}
RtlInitUnicodeString(&UnicodeString, wszModule);
status = RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, FALSE);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiBuildInclExclListForShim] 0x%X Cannot convert UNICODE \"%S\" to ANSI\n",
status, wszModule);
ASSERT(0);
return FALSE;
}
//
// Add the module to the correct list.
//
pInExMod = (PINEXMOD)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(INEXMOD));
if (pInExMod == NULL) {
DPF(dlError,
"[SeiBuildInclExclListForShim] Failed to allocate %d bytes\n",
sizeof(INEXMOD));
return FALSE;
}
len = strlen(szModule) + 1;
pInExMod->pszModule = (char*)(*g_pfnRtlAllocateHeap)(g_pShimHeap, 0, len);
if (pInExMod->pszModule == NULL) {
DPF(dlError,
"[SeiBuildInclExclListForShim] Failed to allocate %d bytes\n", len);
return FALSE;
}
RtlCopyMemory(pInExMod->pszModule, szModule, len);
//
// Link it in the list.
//
if (bInclude) {
pInExMod->pNext = g_pShimInfo[dwCounter].pFirstInclude;
g_pShimInfo[dwCounter].pFirstInclude = pInExMod;
} else {
pInExMod->pNext = g_pShimInfo[dwCounter].pFirstExclude;
g_pShimInfo[dwCounter].pFirstExclude = pInExMod;
}
//
// See if this module is in the other list, and take it out.
//
{
PINEXMOD pInExFree;
PINEXMOD* ppInExModX;
if (bInclude) {
ppInExModX = &g_pShimInfo[dwCounter].pFirstExclude;
} else {
ppInExModX = &g_pShimInfo[dwCounter].pFirstInclude;
}
while (*ppInExModX != NULL) {
if (_stricmp((*ppInExModX)->pszModule, szModule) == 0) {
pInExFree = *ppInExModX;
*ppInExModX = pInExFree->pNext;
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pInExFree->pszModule);
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pInExFree);
break;
}
ppInExModX = &(*ppInExModX)->pNext;
}
}
}
nInEx--;
}
return TRUE;
}
BOOL
SeiCopyGlobalInclList(
IN DWORD dwCounter
)
/*++
Return: STATUS_SUCCESS on success, STATUS_UNSUCCESSFUL on failure.
Desc: This function copies the global inclusion list.
--*/
{
PINEXMOD pInExModX;
SIZE_T len;
PINEXMOD pInExMod = g_pGlobalInclusionList;
//
// Don't do it if we already added it.
//
if (g_pShimInfo[dwCounter].pFirstInclude != NULL) {
return TRUE;
}
while (pInExMod != NULL) {
pInExModX = (PINEXMOD)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(INEXMOD));
if (pInExModX == NULL) {
DPF(dlError,
"[SeiCopyGlobalInclList] (1) Failed to allocate %d bytes\n",
sizeof(INEXMOD));
return FALSE;
}
len = strlen(pInExMod->pszModule) + 1;
pInExModX->pszModule = (char*)(*g_pfnRtlAllocateHeap)(g_pShimHeap, 0, len);
if (pInExModX->pszModule == NULL) {
DPF(dlError,
"[SeiCopyGlobalInclList] (2) Failed to allocate %d bytes\n", len);
return FALSE;
}
RtlCopyMemory(pInExModX->pszModule, pInExMod->pszModule, len);
//
// Link it in the list.
//
pInExModX->pNext = g_pShimInfo[dwCounter].pFirstInclude;
g_pShimInfo[dwCounter].pFirstInclude = pInExModX;
pInExMod = pInExMod->pNext;
}
return TRUE;
}
BOOL
SeiBuildInclListWithOneModule(
IN DWORD dwCounter,
IN LPCSTR lpszModuleToShim
)
{
PINEXMOD pInExMod;
int len;
g_pShimInfo[dwCounter].eInExMode = EXCLUDE_ALL;
//
// Add the module to the correct list.
//
pInExMod = (PINEXMOD)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(INEXMOD));
if (pInExMod == NULL) {
DPF(dlError,
"[SeiBuildInclListWithOneModule] Failed to allocate %d bytes\n",
sizeof(INEXMOD));
return FALSE;
}
len = strlen(lpszModuleToShim) + 1;
pInExMod->pszModule = (char*)(*g_pfnRtlAllocateHeap)(g_pShimHeap, 0, len);
if (pInExMod->pszModule == NULL) {
DPF(dlError,
"[SeiBuildInclListWithOneModule] Failed to allocate %d bytes\n", len);
return FALSE;
}
RtlCopyMemory(pInExMod->pszModule, lpszModuleToShim, len);
//
// Add it to the list.
//
pInExMod->pNext = g_pShimInfo[dwCounter].pFirstInclude;
g_pShimInfo[dwCounter].pFirstInclude = pInExMod;
return TRUE;
}
BOOL
SeiBuildInclExclList(
IN HSDB hSDB, // handle to the database channel
IN TAGREF trShimRef, // The TAGREF to the shim DLL for which to read the
// inclusion or exclusion list from the database.
IN DWORD dwCounter, // Index in the g_pShimInfo array for this shim DLL.
IN LPCWSTR pwszExePath // The full path name of the main EXE.
)
/*++
Return: STATUS_SUCCESS if successful.
Desc: This function builds the inclusion or exclusion list for the specified
shim DLL by reading it from the database.
--*/
{
TAGREF trShim;
//
// Set the default mode to EXCLUDE_SYSTEM32
//
g_pShimInfo[dwCounter].eInExMode = EXCLUDE_SYSTEM32;
trShim = SdbGetShimFromShimRef(hSDB, trShimRef);
if (trShim == TAGREF_NULL) {
DPF(dlError,
"[SeiBuildInclExclList] Corrupt database. Couldn't get the DLL from "
"the LIBRARY section\n");
return FALSE;
}
//
// Make a copy of the global exclusion list first.
//
if (!SeiCopyGlobalInclList(dwCounter)) {
DPF(dlError,
"[SeiBuildInclExclList] SeiCopyGlobalInclList failed\n");
return FALSE;
}
//
// Get DLL specific incl/excl list first.
//
if (!SeiBuildInclExclListForShim(hSDB, trShim, dwCounter, pwszExePath)) {
DPF(dlError,
"[SeiBuildInclExclList] (1) Corrupt database. Couldn't build incl/excl list\n");
return FALSE;
}
//
// Now get the incl/excl specified for this shim within its parent EXE tag.
//
if (!SeiBuildInclExclListForShim(hSDB, trShimRef, dwCounter, pwszExePath)) {
DPF(dlError,
"[SeiBuildInclExclList] (2) Corrupt database. Couldn't build incl/excl list\n");
return FALSE;
}
#if DBG
//
// Print the incl/excl list for this shim.
//
if (g_pShimInfo[dwCounter].pFirstInclude != NULL) {
PINEXMOD pInExMod;
DPF(dlInfo, "[SeiBuildInclExclList] Inclusion list for \"%S\"\n",
g_pShimInfo[dwCounter].pLdrEntry->BaseDllName.Buffer);
pInExMod = g_pShimInfo[dwCounter].pFirstInclude;
while (pInExMod != NULL) {
DPF(dlInfo, "\t\"%s\"\n", pInExMod->pszModule);
pInExMod = pInExMod->pNext;
}
}
if (g_pShimInfo[dwCounter].pFirstExclude != NULL) {
PINEXMOD pInExMod;
DPF(dlInfo, "[SeiBuildInclExclList] Exclusion list for \"%S\"\n",
g_pShimInfo[dwCounter].pLdrEntry->BaseDllName.Buffer);
pInExMod = g_pShimInfo[dwCounter].pFirstExclude;
while (pInExMod != NULL) {
DPF(dlInfo, "\t\"%s\"\n", pInExMod->pszModule);
pInExMod = pInExMod->pNext;
}
}
#endif // DBG
return TRUE;
}
PLDR_DATA_TABLE_ENTRY
SeiGetLoaderEntry(
IN PPEB Peb, // The PEB
IN PVOID pDllBase // The address of the shim DLL to be removed from
// the loader's lists.
)
/*++
Return: Pointer to the loader entry for the shim DLL being removed.
Desc: This function removes the shim DLLs from the loader's lists.
--*/
{
PLIST_ENTRY LdrHead;
PLIST_ENTRY LdrNext;
PLDR_DATA_TABLE_ENTRY LdrEntry;
LdrHead = &Peb->Ldr->InMemoryOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (LdrEntry->DllBase == pDllBase) {
break;
}
LdrNext = LdrEntry->InMemoryOrderLinks.Flink;
}
if (LdrNext != LdrHead) {
return LdrEntry;
}
DPF(dlError, "[SeiGetLoaderEntry] Couldn't find shim DLL in the loader list!\n");
ASSERT(0);
return NULL;
}
void
SeiLoadPatches(
IN HSDB hSDB, // handle to the database channel
IN TAGREF trExe // TAGREF of the EXE for which to get the memory
// patches from the database
)
/*++
Return: void
Desc: This function reads the memory patches from the database and
stores them in the g_pMemoryPatches array.
--*/
{
TAGREF trPatchRef;
DWORD dwSize;
//
// Read the patches for this EXE.
//
trPatchRef = SdbFindFirstTagRef(hSDB, trExe, TAG_PATCH_REF);
while (trPatchRef != TAGREF_NULL) {
//
// Get the size of this patch.
//
dwSize = 0;
SdbReadPatchBits(hSDB, trPatchRef, NULL, &dwSize);
if (dwSize == 0) {
DPF(dlError, "[SeiLoadPatches] returned 0 for patch size.\n");
ASSERT(dwSize != 0);
return;
}
if (g_dwMemoryPatchCount == SHIM_MAX_PATCH_COUNT) {
DPF(dlError, "[SeiLoadPatches] Too many patches.\n");
return;
}
//
// Allocate memory for the patch bits.
//
g_pMemoryPatches[g_dwMemoryPatchCount] = (PBYTE)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
dwSize);
if (g_pMemoryPatches[g_dwMemoryPatchCount] == NULL) {
DPF(dlError, "[SeiLoadPatches] Failed to allocate %d bytes for patch.\n",
dwSize);
return;
}
//
// Read the patch bits from the database.
//
if (!SdbReadPatchBits(hSDB,
trPatchRef,
g_pMemoryPatches[g_dwMemoryPatchCount],
&dwSize)) {
DPF(dlError, "[SeiLoadPatches] Failure getting patch bits.\n");
ASSERT(0);
return;
}
g_dwMemoryPatchCount++;
//
// Get the next patch.
//
trPatchRef = SdbFindNextTagRef(hSDB, trExe, trPatchRef);
}
}
BOOL
SeiGetModuleHandle(
IN LPSTR pszModule,
OUT PVOID* pModuleHandle
)
/*++
Return: void
Desc: This function loops through the loaded modules and gets the
handle of the specified named module.
--*/
{
ANSI_STRING AnsiString;
WCHAR wszDllName[128];
UNICODE_STRING UnicodeString = { 0, sizeof(wszDllName), wszDllName };
NTSTATUS status;
RtlInitAnsiString(&AnsiString, pszModule);
if (!NT_SUCCESS(RtlAnsiStringToUnicodeString(&UnicodeString,
&AnsiString,
FALSE))){
DPF(dlError,
"[SeiGetModuleHandle] Failed to convert \"%s\" to UNICODE.\n",
pszModule);
return FALSE;
}
status = LdrGetDllHandle(NULL,
NULL,
&UnicodeString,
pModuleHandle);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiGetModuleHandle] Failed to get the handle for \"%s\".\n",
pszModule);
return FALSE;
}
return TRUE;
}
void
SeiRemoveDll(
IN LPSTR pszBaseDllName // the named of the unloaded module
)
/*++
Return: void
Desc: This function loops through the loaded shims info and resets
the resolved APIs that belong to the specified module that had
just been unloaded.
--*/
{
DWORD i, j;
for (i = 0; i < g_dwShimsCount; i++) {
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
if (g_pHookArray[i][j].pszModule != NULL &&
_strcmpi(g_pHookArray[i][j].pszModule, pszBaseDllName) == 0) {
if ((ULONG_PTR)g_pHookArray[i][j].pszFunctionName < 0x0000FFFF) {
DPF(dlWarning,
"[SeiRemoveDll] \"%s!#%d\" not resolved again\n",
g_pHookArray[i][j].pszModule,
g_pHookArray[i][j].pszFunctionName);
} else {
DPF(dlWarning,
"[SeiRemoveDll] \"%s!%s\" not resolved again\n",
g_pHookArray[i][j].pszModule,
g_pHookArray[i][j].pszFunctionName);
}
g_pHookArray[i][j].pfnOld = NULL;
g_pShimInfo[i].dwFlags &= ~SIF_RESOLVED;
}
}
}
}
BOOL
SeiGetModuleByAddress(
PVOID pAddress,
CHAR* pszModuleName,
BOOL* pbInSystem32
)
{
DWORD i;
for (i = 0; i < g_dwHookedModuleCount; i++) {
if ((ULONG_PTR)pAddress >= (ULONG_PTR)g_hHookedModules[i].pDllBase &&
(ULONG_PTR)pAddress < (ULONG_PTR)g_hHookedModules[i].pDllBase + (ULONG_PTR)g_hHookedModules[i].ulSizeOfImage) {
//
// We found the DLL in the hooked list.
//
strcpy(pszModuleName, g_hHookedModules[i].szModuleName);
*pbInSystem32 = g_hHookedModules[i].bInSystem32;
return TRUE;
}
}
return FALSE;
}
PVOID
StubGetProcAddress(
IN HMODULE hMod,
IN LPSTR pszProc
)
/*++
Return: The address of the function specified.
Desc: Intercepts calls to GetProcAddress to look for hooked functions. If
a function was hooked, return the top-most stub function.
--*/
{
DWORD i, j;
DWORD dwDllIndex;
PHOOKAPI pTopHookAPI = NULL;
PVOID pfn;
PFNGETPROCADDRESS pfnOld;
PVOID retAddress = NULL;
ULONG ulHash;
CHAR szBaseDllName[MAX_PATH];
BOOL bInSystem32;
pfnOld = g_IntHookAPI[IHA_GetProcAddress].pfnOld;
pfn = (*pfnOld)(hMod, pszProc);
if (pfn == NULL) {
return NULL;
}
for (i = 0; i < g_dwShimsCount; i++) {
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
if (g_pHookArray[i][j].pfnOld == pfn) {
pTopHookAPI = SeiConstructChain(pfn, &dwDllIndex);
if (pTopHookAPI == NULL) {
DPF(dlError,
"[StubGetProcAddress] failed to construct the chain for pfn 0x%p\n",
pfn);
return pfn;
}
//
// Probably we care about inclusion/exclusion lists here as well.
//
RtlCaptureStackBackTrace(1, 1, &retAddress, &ulHash);
DPF(dlPrint,
"[StubGetProcAddress] Stack capture caller 0x%p\n",
retAddress);
if (retAddress && SeiGetModuleByAddress(retAddress, szBaseDllName, &bInSystem32)) {
if (SeiIsExcluded(szBaseDllName, pTopHookAPI, bInSystem32)) {
return pfn;
}
}
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
DPF(dlInfo,
"[StubGetProcAddress] called for \"%s!#%d\" 0x%p changed to 0x%p\n",
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName,
pfn,
pTopHookAPI->pfnNew);
} else {
DPF(dlInfo,
"[StubGetProcAddress] called for \"%s!%s\" 0x%p changed to 0x%p\n",
pTopHookAPI->pszModule,
pTopHookAPI->pszFunctionName,
pfn,
pTopHookAPI->pfnNew);
}
return pTopHookAPI->pfnNew;
}
}
}
return pfn;
}
#ifdef SE_WIN2K
//
// The Win2k engine needs to hook a few other APIs as well.
//
HMODULE
StubLoadLibraryA(
IN LPCSTR pszModule
)
{
HMODULE hMod;
PFNLOADLIBRARYA pfnOld;
DWORD i;
pfnOld = g_IntHookAPI[IHA_LoadLibraryA].pfnOld;
hMod = (*pfnOld)(pszModule);
if (hMod == NULL) {
return NULL;
}
//
// Was this DLL already loaded ?
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (hMod == g_hHookedModules[i].pDllBase) {
DPF(dlInfo,
"[StubLoadLibraryA] DLL \"%s\" was already loaded.\n",
pszModule);
return hMod;
}
}
PatchNewModules(FALSE);
return hMod;
}
HMODULE
StubLoadLibraryW(
IN LPCWSTR pszModule
)
{
HMODULE hMod;
PFNLOADLIBRARYW pfnOld;
DWORD i;
pfnOld = g_IntHookAPI[IHA_LoadLibraryW].pfnOld;
hMod = (*pfnOld)(pszModule);
if (hMod == NULL) {
return NULL;
}
//
// Was this DLL already loaded ?
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (hMod == g_hHookedModules[i].pDllBase) {
DPF(dlInfo,
"[StubLoadLibraryW] DLL \"%S\" was already loaded.\n",
pszModule);
return hMod;
}
}
PatchNewModules(FALSE);
return hMod;
}
HMODULE
StubLoadLibraryExA(
IN LPCSTR pszModule,
IN HANDLE hFile,
IN DWORD dwFlags
)
{
HMODULE hMod;
PFNLOADLIBRARYEXA pfnOld;
DWORD i;
pfnOld = g_IntHookAPI[IHA_LoadLibraryExA].pfnOld;
hMod = (*pfnOld)(pszModule, hFile, dwFlags);
if (hMod == NULL) {
return NULL;
}
//
// Was this DLL already loaded ?
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (hMod == g_hHookedModules[i].pDllBase) {
DPF(dlInfo,
"[StubLoadLibraryExA] DLL \"%s\" was already loaded.\n",
pszModule);
return hMod;
}
}
PatchNewModules(FALSE);
return hMod;
}
HMODULE
StubLoadLibraryExW(
IN LPCWSTR pszModule,
IN HANDLE hFile,
IN DWORD dwFlags
)
{
HMODULE hMod;
PFNLOADLIBRARYEXW pfnOld;
DWORD i;
pfnOld = g_IntHookAPI[IHA_LoadLibraryExW].pfnOld;
hMod = (*pfnOld)(pszModule, hFile, dwFlags);
if (hMod == NULL) {
return NULL;
}
//
// Was this DLL already loaded ?
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (hMod == g_hHookedModules[i].pDllBase) {
DPF(dlInfo,
"[StubLoadLibraryExW] DLL \"%S\" was already loaded.\n",
pszModule);
return hMod;
}
}
PatchNewModules(FALSE);
return hMod;
}
BOOL
SeiIsDllLoaded(
IN HMODULE hMod,
IN PLDR_DATA_TABLE_ENTRY* pLdrEntry
)
{
PPEB Peb = NtCurrentPeb();
PLIST_ENTRY LdrHead;
PLIST_ENTRY LdrNext;
DWORD i;
//
// Loop through the loaded modules.
//
LdrHead = &Peb->Ldr->InMemoryOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
PLDR_DATA_TABLE_ENTRY LdrEntry;
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (LdrEntry->DllBase == hMod) {
*pLdrEntry = LdrEntry;
return TRUE;
}
LdrNext = LdrEntry->InMemoryOrderLinks.Flink;
}
return FALSE;
}
BOOL
StubFreeLibrary(
IN HMODULE hLibModule
)
{
DWORD i, j;
PFNFREELIBRARY pfnOld;
BOOL bRet;
PLDR_DATA_TABLE_ENTRY LdrEntry;
char szBaseDllName[128];
pfnOld = g_IntHookAPI[IHA_FreeLibrary].pfnOld;
bRet = (*pfnOld)(hLibModule);
//
// See if this DLL is one that we hooked.
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (g_hHookedModules[i].pDllBase == hLibModule) {
break;
}
}
if (i >= g_dwHookedModuleCount) {
return bRet;
}
//
// Is the DLL still loaded ?
//
if (SeiIsDllLoaded(hLibModule, &LdrEntry)) {
DPF(dlInfo,
"[StubFreeLibrary] Dll \"%S\" still loaded.\n",
LdrEntry->BaseDllName.Buffer);
return bRet;
}
strcpy(szBaseDllName, g_hHookedModules[i].szModuleName);
DPF(dlInfo,
"[StubFreeLibrary] Removing hooked DLL 0x%p \"%s\"\n",
hLibModule,
szBaseDllName);
//
// Take it out of the list of hooked modules.
//
for (j = i; j < g_dwHookedModuleCount - 1; j++) {
RtlCopyMemory(g_hHookedModules + j, g_hHookedModules + j + 1, sizeof(HOOKEDMODULE));
}
g_hHookedModules[j].pDllBase = NULL;
strcpy(g_hHookedModules[j].szModuleName, "removed!");
g_dwHookedModuleCount--;
//
// Remove the pfnOld from the HOOKAPIs that were
// resolved to this DLL
//
SeiRemoveDll(szBaseDllName);
return bRet;
}
#endif // SE_WIN2K
BOOL
SeiInitFileLog(
IN LPCWSTR pwszAppName // The full path of the starting EXE
)
/*++
Return: TRUE if the log was initialized.
Desc: This function checks an environment variable to determine if logging
is enabled. If so, it will append a header that tells a new app is
started.
--*/
{
NTSTATUS status;
UNICODE_STRING EnvName;
UNICODE_STRING EnvValue;
UNICODE_STRING FilePath;
UNICODE_STRING NtSystemRoot;
WCHAR wszEnvValue[128];
WCHAR wszLogFile[MAX_PATH];
HANDLE hfile;
OBJECT_ATTRIBUTES ObjA;
LARGE_INTEGER liOffset;
RTL_RELATIVE_NAME RelativeName;
ULONG uBytes;
char szHeader[512];
char szFormatHeader[] = "-------------------------------------------\r\n"
" Log \"%S\"\r\n"
"-------------------------------------------\r\n";
IO_STATUS_BLOCK ioStatusBlock;
RtlInitUnicodeString(&EnvName, L"SHIM_FILE_LOG");
EnvValue.Buffer = wszEnvValue;
EnvValue.Length = 0;
EnvValue.MaximumLength = sizeof(wszEnvValue);
status = RtlQueryEnvironmentVariable_U(NULL, &EnvName, &EnvValue);
if (!NT_SUCCESS(status)) {
DPF(dlInfo, "[SeiInitFileLog] Logging not enabled\n");
return FALSE;
}
FilePath.Buffer = wszLogFile;
FilePath.Length = 0;
FilePath.MaximumLength = sizeof(wszLogFile);
RtlInitUnicodeString(&NtSystemRoot, USER_SHARED_DATA->NtSystemRoot);
RtlAppendUnicodeStringToString(&FilePath, &NtSystemRoot);
RtlAppendUnicodeToString(&FilePath, L"\\AppPatch\\");
RtlAppendUnicodeStringToString(&FilePath, &EnvValue);
if (!RtlDosPathNameToNtPathName_U(FilePath.Buffer,
&FilePath,
NULL,
&RelativeName)) {
DPF(dlError,
"[SeiInitFileLog] Failed to convert path name \"%S\"\n",
wszLogFile);
return FALSE;
}
InitializeObjectAttributes(&ObjA,
&FilePath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
//
// Open/Create the log file.
//
status = NtCreateFile(&hfile,
FILE_APPEND_DATA | SYNCHRONIZE,
&ObjA,
&ioStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
RtlFreeUnicodeString(&FilePath);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiInitFileLog] 0x%X Cannot open/create log file \"%S\"\n",
status, wszLogFile);
return FALSE;
}
//
// Now write a new line in the log file
//
ioStatusBlock.Status = 0;
ioStatusBlock.Information = 0;
liOffset.LowPart = 0;
liOffset.HighPart = 0;
uBytes = (ULONG)sprintf(szHeader, szFormatHeader, pwszAppName);
status = NtWriteFile(hfile,
NULL,
NULL,
NULL,
&ioStatusBlock,
(PVOID)szHeader,
uBytes,
&liOffset,
NULL);
NtClose(hfile);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiInitFileLog] 0x%X Cannot write into the log file \"%S\"\n",
status, wszLogFile);
return FALSE;
}
return TRUE;
}
LPWSTR
SeiGetLayerName(
IN HSDB hSDB,
IN TAGREF trLayer
)
/*++
Return: BUGBUG
Desc: BUGBUG
--*/
{
PDB pdb;
TAGID tiLayer, tiName;
LPWSTR pwszName;
if (!SdbTagRefToTagID(hSDB, trLayer, &pdb, &tiLayer)) {
DPF(dlError, "[SeiGetLayerName] Failed to get tag id from tag ref\n");
return NULL;
}
tiName = SdbFindFirstTag(pdb, tiLayer, TAG_NAME);
if (tiName == TAGID_NULL) {
DPF(dlError,
"[SeiGetLayerName] Failed to get the name tag id\n");
return NULL;
}
pwszName = SdbGetStringTagPtr(pdb, tiName);
if (pwszName == NULL) {
DPF(dlError,
"[SeiGetLayerName] Cannot read the name of the layer tag\n");
}
return pwszName;
}
void
SeiClearLayerEnvVar(
void
)
/*++
Return: BUGBUG
Desc: BUGBUG
--*/
{
UNICODE_STRING EnvName;
NTSTATUS status;
RtlInitUnicodeString(&EnvName, L"__COMPAT_LAYER");
status = RtlSetEnvironmentVariable(NULL, &EnvName, NULL);
if (NT_SUCCESS(status)) {
DPF(dlInfo, "[SeiClearLayerEnvVar] Cleared env var __COMPAT_LAYER.\n");
} else {
DPF(dlError, "[SeiClearLayerEnvVar] Failed to clear __COMPAT_LAYER. 0x%X\n", status);
}
}
BOOL
SeiIsLayerEnvVarSet(
void
)
/*++
Return: BUGBUG
Desc: BUGBUG
--*/
{
NTSTATUS status;
UNICODE_STRING EnvName;
UNICODE_STRING EnvValue;
WCHAR wszEnvValue[128];
RtlInitUnicodeString(&EnvName, L"__COMPAT_LAYER");
EnvValue.Buffer = wszEnvValue;
EnvValue.Length = 0;
EnvValue.MaximumLength = sizeof(wszEnvValue);
status = RtlQueryEnvironmentVariable_U(NULL, &EnvName, &EnvValue);
return NT_SUCCESS(status);
}
BOOL
SeiCheckLayerEnvVarFlags(
BOOL* pbApplyExes,
BOOL* pbApplyToSystemExes
)
/*++
Return: BUGBUG
Desc: BUGBUG
--*/
{
NTSTATUS status;
UNICODE_STRING EnvName;
UNICODE_STRING EnvValue;
WCHAR wszEnvValue[128] = L"";
LPWSTR pwszEnvTemp;
if (pbApplyExes) {
*pbApplyExes = TRUE;
}
if (pbApplyToSystemExes) {
*pbApplyToSystemExes = FALSE;
}
RtlInitUnicodeString(&EnvName, L"__COMPAT_LAYER");
EnvValue.Buffer = wszEnvValue;
EnvValue.Length = 0;
EnvValue.MaximumLength = sizeof(wszEnvValue);
status = RtlQueryEnvironmentVariable_U(NULL, &EnvName, &EnvValue);
//
// Skip over and handle special flag characters
// '!' means don't use any EXE entries from the DB
// '#' means go ahead and apply layers to system EXEs
//
if (NT_SUCCESS(status)) {
pwszEnvTemp = EnvValue.Buffer;
while (*pwszEnvTemp) {
if (*pwszEnvTemp == L'!') {
if (pbApplyExes) {
*pbApplyExes = FALSE;
}
} else if (*pwszEnvTemp == L'#') {
if (pbApplyToSystemExes) {
*pbApplyToSystemExes = TRUE;
}
} else {
break;
}
pwszEnvTemp++;
}
}
return NT_SUCCESS(status);
}
void
SeiSetLayerEnvVar(
WCHAR* pwszName
)
/*++
Return: BUGBUG
Desc: BUGBUG
--*/
{
NTSTATUS status;
UNICODE_STRING EnvName;
UNICODE_STRING EnvValue;
WCHAR wszEnvValue[128];
RtlInitUnicodeString(&EnvName, L"__COMPAT_LAYER");
EnvValue.Buffer = wszEnvValue;
EnvValue.Length = 0;
EnvValue.MaximumLength = sizeof(wszEnvValue);
status = RtlQueryEnvironmentVariable_U(NULL, &EnvName, &EnvValue);
if (NT_SUCCESS(status) && (EnvValue.Buffer[0] == L'!' || EnvValue.Buffer[1] == L'!')) {
//
// There should be no way to add extra layers to the list,
// So we should leave it alone.
//
return;
}
//
// We need to set the environment variable.
//
if (pwszName != NULL) {
RtlInitUnicodeString(&EnvValue, pwszName);
status = RtlSetEnvironmentVariable(NULL, &EnvName, &EnvValue);
if (NT_SUCCESS(status)) {
DPF(dlInfo, "[SeiSetLayerEnvVar] Env var set __COMPAT_LAYER=\"%S\"\n", pwszName);
} else {
DPF(dlError, "[SeiSetLayerEnvVar] Failed to set __COMPAT_LAYER. 0x%X\n", status);
}
}
}
BOOL
SeiAddInternalHooks(
DWORD dwCounter
)
/*++
Return: FALSE if the internal hooks have been already added, TRUE otherwise
Desc: BUGBUG
--*/
{
if (g_bInternalHooksUsed) {
return FALSE;
}
g_bInternalHooksUsed = TRUE;
ZeroMemory(g_IntHookAPI, sizeof(HOOKAPI) * IHA_COUNT);
ZeroMemory(g_IntHookEx, sizeof(HOOKAPIEX) * IHA_COUNT);
g_IntHookAPI[IHA_GetProcAddress].pszModule = "kernel32.dll";
g_IntHookAPI[IHA_GetProcAddress].pszFunctionName = "GetProcAddress";
g_IntHookAPI[IHA_GetProcAddress].pfnNew = (PVOID)StubGetProcAddress;
g_IntHookAPI[IHA_GetProcAddress].pHookEx = &g_IntHookEx[IHA_GetProcAddress];
g_IntHookAPI[IHA_GetProcAddress].pHookEx->dwShimID = dwCounter;
#ifdef SE_WIN2K
g_IntHookAPI[IHA_LoadLibraryA].pszModule = "kernel32.dll";
g_IntHookAPI[IHA_LoadLibraryA].pszFunctionName = "LoadLibraryA";
g_IntHookAPI[IHA_LoadLibraryA].pfnNew = (PVOID)StubLoadLibraryA;
g_IntHookAPI[IHA_LoadLibraryA].pHookEx = &g_IntHookEx[IHA_LoadLibraryA];
g_IntHookAPI[IHA_LoadLibraryA].pHookEx->dwShimID = dwCounter;
g_IntHookAPI[IHA_LoadLibraryW].pszModule = "kernel32.dll";
g_IntHookAPI[IHA_LoadLibraryW].pszFunctionName = "LoadLibraryW";
g_IntHookAPI[IHA_LoadLibraryW].pfnNew = (PVOID)StubLoadLibraryW;
g_IntHookAPI[IHA_LoadLibraryW].pHookEx = &g_IntHookEx[IHA_LoadLibraryW];
g_IntHookAPI[IHA_LoadLibraryW].pHookEx->dwShimID = dwCounter;
g_IntHookAPI[IHA_LoadLibraryExA].pszModule = "kernel32.dll";
g_IntHookAPI[IHA_LoadLibraryExA].pszFunctionName = "LoadLibraryExA";
g_IntHookAPI[IHA_LoadLibraryExA].pfnNew = (PVOID)StubLoadLibraryExA;
g_IntHookAPI[IHA_LoadLibraryExA].pHookEx = &g_IntHookEx[IHA_LoadLibraryExA];
g_IntHookAPI[IHA_LoadLibraryExA].pHookEx->dwShimID = dwCounter;
g_IntHookAPI[IHA_LoadLibraryExW].pszModule = "kernel32.dll";
g_IntHookAPI[IHA_LoadLibraryExW].pszFunctionName = "LoadLibraryExW";
g_IntHookAPI[IHA_LoadLibraryExW].pfnNew = (PVOID)StubLoadLibraryExW;
g_IntHookAPI[IHA_LoadLibraryExW].pHookEx = &g_IntHookEx[IHA_LoadLibraryExW];
g_IntHookAPI[IHA_LoadLibraryExW].pHookEx->dwShimID = dwCounter;
g_IntHookAPI[IHA_FreeLibrary].pszModule = "kernel32.dll";
g_IntHookAPI[IHA_FreeLibrary].pszFunctionName = "FreeLibrary";
g_IntHookAPI[IHA_FreeLibrary].pfnNew = (PVOID)StubFreeLibrary;
g_IntHookAPI[IHA_FreeLibrary].pHookEx = &g_IntHookEx[IHA_FreeLibrary];
g_IntHookAPI[IHA_FreeLibrary].pHookEx->dwShimID = dwCounter;
#endif // SE_WIN2K
//
// Add the info for our internal hook
//
g_pShimInfo[dwCounter].dwHookedAPIs = IHA_COUNT;
g_pShimInfo[dwCounter].pDllBase = g_pShimEngModHandle;
g_pShimInfo[dwCounter].pLdrEntry = g_pShimEngLdrEntry;
g_pShimInfo[dwCounter].eInExMode = INCLUDE_ALL;
wcscpy(g_pShimInfo[dwCounter].wszName, L"SHIMENG.DLL");
g_pHookArray[dwCounter] = g_IntHookAPI;
return TRUE;
}
void
NotifyShims(
int nReason,
UINT_PTR extraInfo
)
{
DWORD i, j;
NTSTATUS status;
ANSI_STRING ProcedureNameString;
PFNNOTIFYSHIMS pfnNotifyShims = NULL;
for (i = 0; i < g_dwShimsCount; i++) {
for (j = 0; j < i; j++) {
if (g_pShimInfo[i].pDllBase == g_pShimInfo[j].pDllBase) {
break;
}
}
if (i == j && g_pShimInfo[i].pLdrEntry != g_pShimEngLdrEntry) {
//
// Get the NotifyShims entry point
//
RtlInitString(&ProcedureNameString, "NotifyShims");
status = LdrGetProcedureAddress(g_pShimInfo[i].pDllBase,
&ProcedureNameString,
0,
(PVOID*)&pfnNotifyShims);
if (!NT_SUCCESS(status) || pfnNotifyShims == NULL) {
DPF(dlError,
"[NotifyShims] Failed to get 'NotifyShims' address, DLL \"%S\"\n",
g_pShimInfo[i].wszName);
} else {
//
// Call the notification function.
//
(*pfnNotifyShims)(nReason, extraInfo);
}
}
}
return;
}
void
NotifyShimDlls(
void
)
/*++
Return: void
Desc: Notify the shim DLLs that all the static linked modules have run
their init routines.
--*/
{
NotifyShims(SN_STATIC_DLLS_INITIALIZED, 0);
#ifdef SE_WIN2K
//
// On Win2k we need to restore the code at the entry point.
//
RestoreOriginalCode();
#endif // SE_WIN2K
return;
}
BOOL
SeiGetExeName(
PPEB Peb,
LPWSTR pwszExeName
)
/*++
Return: BUGBUG
Desc: BUGBUG
--*/
{
PLDR_DATA_TABLE_ENTRY Entry;
PLIST_ENTRY Head;
Head = &Peb->Ldr->InLoadOrderModuleList;
Head = Head->Flink;
Entry = CONTAINING_RECORD(Head, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
wcscpy(pwszExeName, Entry->FullDllName.Buffer);
//
// Save the exe name in our global
//
wcscpy(g_szExeName, Entry->BaseDllName.Buffer);
#ifdef SE_WIN2K
InjectNotificationCode(Entry->EntryPoint);
#endif // SE_WIN2K
return TRUE;
}
#ifndef SE_WIN2K
int
SE_IsShimDll(
IN PVOID pDllBase // The address of a loaded DLL
)
/*++
Return: TRUE if the DLL is one of our shim DLLs
Desc: This function checks to see if a DLL is one of the shim DLLs
loaded in this process.
--*/
{
DWORD i;
for (i = 0; i < g_dwShimsCount; i++) {
if (g_pShimInfo[i].pDllBase == pDllBase) {
return 1;
}
}
//
// Special hack for the apphelp case
//
if (pDllBase == g_hApphelpDllHelper) {
return 1;
}
return 0;
}
void
SeiSetEntryProcessed(
IN PPEB Peb
)
/*++
Return: void
Desc: This function hacks the loader list of loaded DLLs and marks them
to tell the loader that they executed their init routines even if
that is not the case. This needs to be done so that our shim mechanism
is effective before the staticly loaded module get to execute their
init routines.
--*/
{
PLIST_ENTRY LdrHead;
PLIST_ENTRY LdrNext;
PLDR_DATA_TABLE_ENTRY LdrEntry;
if (g_bComPlusImage) {
//
// COM+ images mess with the loader in ntdll. Don't step on ntdll's
// toes by messing with LDRP_ENTRY_PROCESSED.
//
return;
}
//
// Loop through the loaded modules and set LDRP_ENTRY_PROCESSED as
// needed. Don't do this for ntdll.dll and kernel32.dll.
// This needs to be done so when we load the shim DLLs the routines for
// the statically linked libraries don't get called.
//
LdrHead = &Peb->Ldr->InInitializationOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
if (RtlCompareUnicodeString(&Kernel32String, &LdrEntry->BaseDllName, TRUE) != 0 &&
RtlCompareUnicodeString(&VerifierdllString, &LdrEntry->BaseDllName, TRUE) != 0 &&
RtlCompareUnicodeString(&NtdllString, &LdrEntry->BaseDllName, TRUE) != 0 &&
!SE_IsShimDll(LdrEntry->DllBase) &&
_wcsicmp(LdrEntry->BaseDllName.Buffer, g_wszShimDllInLoading) != 0) {
LdrEntry->Flags |= LDRP_ENTRY_PROCESSED;
DPF(dlWarning,
"[SeiSetEntryProcessed] Touching 0x%X \"%S\"\n",
LdrEntry->DllBase,
LdrEntry->BaseDllName.Buffer);
} else {
DPF(dlWarning,
"[SeiSetEntryProcessed] Don't mess with 0x%X \"%S\"\n",
LdrEntry->DllBase,
LdrEntry->BaseDllName.Buffer);
}
LdrNext = LdrEntry->InInitializationOrderLinks.Flink;
}
#if DBG
DPF(dlInfo, "[SeiSetEntryProcessed] In memory:\n");
LdrHead = &Peb->Ldr->InMemoryOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
DPF(dlInfo,
"\t0x%X \"%S\"\n",
LdrEntry->DllBase,
LdrEntry->BaseDllName.Buffer);
LdrNext = LdrEntry->InMemoryOrderLinks.Flink;
}
DPF(dlInfo, "\n[SeiSetEntryProcessed] In load:\n");
LdrHead = &Peb->Ldr->InLoadOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
DPF(dlInfo,
"\t0x%X \"%S\"\n",
LdrEntry->DllBase,
LdrEntry->BaseDllName.Buffer);
LdrNext = LdrEntry->InLoadOrderLinks.Flink;
}
#endif // DBG
}
void
SeiResetEntryProcessed(
PPEB Peb
)
/*++
Return: void
Desc: This function restores the flag in the loader's list
of loaded DLLs that tells they need to run their init
routines (see LdrpSetEntryProcessed)
--*/
{
PLIST_ENTRY LdrHead;
PLIST_ENTRY LdrNext;
if (g_bComPlusImage) {
//
// COM+ images mess with the loader in ntdll. Don't step on ntdll's
// toes by messing with LDRP_ENTRY_PROCESSED.
//
return;
}
//
// Loop through the loaded modules and remove LDRP_ENTRY_PROCESSED as
// needed. Don't do this for ntdll.dll, kernel32.dll and all the shim DLLs
//
LdrHead = &Peb->Ldr->InInitializationOrderModuleList;
LdrNext = LdrHead->Flink;
while (LdrNext != LdrHead) {
PLDR_DATA_TABLE_ENTRY LdrEntry;
LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
if (RtlCompareUnicodeString(&Kernel32String, &LdrEntry->BaseDllName, TRUE) != 0 &&
RtlCompareUnicodeString(&VerifierdllString, &LdrEntry->BaseDllName, TRUE) != 0 &&
RtlCompareUnicodeString(&NtdllString, &LdrEntry->BaseDllName, TRUE) != 0 &&
LdrEntry->DllBase != g_pShimEngModHandle &&
!SE_IsShimDll(LdrEntry->DllBase)) {
LdrEntry->Flags &= ~LDRP_ENTRY_PROCESSED;
DPF(dlWarning,
"[SeiResetEntryProcessed] Reseting \"%S\"\n",
LdrEntry->BaseDllName.Buffer);
} else {
DPF(dlWarning,
"[SeiResetEntryProcessed] Don't mess with \"%S\"\n",
LdrEntry->BaseDllName.Buffer);
}
LdrNext = LdrEntry->InInitializationOrderLinks.Flink;
}
}
void
SE_GetProcAddress(
PVOID* pProcedureAddress // Pointer containing the return from the original
// LdrGetProcedureAddress. This functions fils this
// pointer with the eventual replacement from the
// HOOKAPI arrays
)
/*++
Return: void
Desc: Scans HOOKAPI arrays for pfnOld and return pfnNew if this is
a shimmed API
--*/
{
return;
UNREFERENCED_PARAMETER(pProcedureAddress);
}
void
SE_DllLoaded(
PLDR_DATA_TABLE_ENTRY LdrEntry
)
{
if (g_bShimInitialized) {
#if DBG
DWORD i;
//
// Was this DLL already loaded ?
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (LdrEntry->DllBase == g_hHookedModules[i].pDllBase) {
DPF(dlError,
"[SE_DllLoaded] DLL \"%s\" was already loaded.\n",
g_hHookedModules[i].szModuleName);
}
}
#endif // DBG
DPF(dlInfo,
"[SE_DllLoaded] AFTER INIT. loading DLL \"%S\".\n",
LdrEntry->BaseDllName.Buffer);
PatchNewModules(FALSE);
} else if (g_bShimDuringInit) {
DPF(dlInfo,
"[SE_DllLoaded] INIT. loading DLL \"%S\".\n",
LdrEntry->BaseDllName.Buffer);
SeiSetEntryProcessed(NtCurrentPeb());
}
return;
UNREFERENCED_PARAMETER(LdrEntry);
}
void
SE_DllUnloaded(
PLDR_DATA_TABLE_ENTRY Entry // The pointer to the loader entry for the DLL that is
// being unloaded.
)
/*++
Return: void
Desc: This notification comes from LdrUnloadDll. This function looks up
to see if we have HOOKAPI that have their original functions in
this DLL. If that is the case then HOOKAPI.pfnOld is set back to
NULL.
--*/
{
DWORD i, j;
CHAR szBaseDllName[128];
ANSI_STRING AnsiString = { 0, sizeof(szBaseDllName), szBaseDllName };
if (g_dwShimsCount == 0) {
return;
}
//
// See if this DLL is one that we hooked.
//
for (i = 0; i < g_dwHookedModuleCount; i++) {
if (g_hHookedModules[i].pDllBase == Entry->DllBase) {
break;
}
}
if (i >= g_dwHookedModuleCount) {
return;
}
DPF(dlWarning,
"[SEi_DllUnloaded] Removing hooked DLL 0x%p \"%S\"\n",
Entry->DllBase,
Entry->BaseDllName.Buffer);
//
// Take it out of the list of hooked modules.
//
for (j = i; j < g_dwHookedModuleCount - 1; j++) {
RtlCopyMemory(g_hHookedModules + j, g_hHookedModules + j + 1, sizeof(HOOKEDMODULE));
}
g_hHookedModules[j].pDllBase = NULL;
strcpy(g_hHookedModules[j].szModuleName, "removed!");
g_dwHookedModuleCount--;
//
// Remove the pfnOld from the HOOKAPIs that were
// resolved to this DLL.
//
if (!NT_SUCCESS(RtlUnicodeStringToAnsiString(&AnsiString, &Entry->BaseDllName, FALSE))) {
DPF(dlError,
"[SEi_DllUnloaded] Cannot convert \"%S\" to ANSI\n",
Entry->BaseDllName.Buffer);
return;
}
SeiRemoveDll(szBaseDllName);
}
BOOLEAN
SE_InstallAfterInit(
IN PUNICODE_STRING UnicodeImageName, // The name of the starting EXE
IN PVOID pShimExeData // The pointer provided by apphelp.dll
)
/*++
Return: FALSE if the shim engine should be unloaded, TRUE otherwise
Desc: Calls the notification function for static linked modules.
--*/
{
NotifyShimDlls();
if (g_dwShimsCount == 0 && g_dwMemoryPatchCount == 0) {
return FALSE;
}
return TRUE;
UNREFERENCED_PARAMETER(UnicodeImageName);
UNREFERENCED_PARAMETER(pShimExeData);
}
#endif // SE_WIN2K
LPWSTR
SeiGetShortName(
IN LPCWSTR pwszDLLPath
)
/*++
Return: The pointer to the short name from the full path
Desc: Gets the pointer to the short name from the full path.
--*/
{
LPWSTR pwsz;
pwsz = (LPWSTR)pwszDLLPath + wcslen(pwszDLLPath);
while (pwsz >= pwszDLLPath) {
if (*pwsz == L'\\') {
break;
}
pwsz--;
}
return pwsz + 1;
}
BOOL
SeiInitGlobals(
IN LPCWSTR lpszFullPath
)
{
PPEB Peb = NtCurrentPeb();
BOOL bResult;
BOOL bWow64 = FALSE;
if (g_bInitGlobals) {
return TRUE;
}
//
// Nab the system32 and windows directory path and length.
//
wcscpy(g_szSystem32, USER_SHARED_DATA->NtSystemRoot);
IsWow64Process(GetCurrentProcess(), &bWow64);
if (bWow64) {
wcscat(g_szSystem32, L"\\SysWow64\\");
} else {
wcscat(g_szSystem32, L"\\System32\\");
}
g_dwSystem32StrLen = wcslen(g_szSystem32);
wcscpy(g_szSxS, USER_SHARED_DATA->NtSystemRoot);
wcscat(g_szSxS, L"\\WinSxS\\");
g_dwSxSStrLen = wcslen(g_szSxS);
wcscpy(g_szWindir, USER_SHARED_DATA->NtSystemRoot);
wcscat(g_szWindir, L"\\");
g_dwWindirStrLen = wcslen(g_szWindir);
wcscpy(g_szCmdExePath, g_szSystem32);
wcscat(g_szCmdExePath, L"cmd.exe");
//
// Initialize our global function pointers.
//
// This is done because these functions may be hooked by a shim and
// we don't want to trip over a shim hook internally. If one of these
// functions is hooked, these global pointers will be overwritten
// with thunk addresses.
//
g_pfnRtlAllocateHeap = RtlAllocateHeap;
g_pfnRtlFreeHeap = RtlFreeHeap;
//
// Set up our own shim heap.
//
g_pShimHeap = RtlCreateHeap(HEAP_GROWABLE,
0, // location isn't important
64 * 1024, // 64k is the initial heap size
8 * 1024, // bring in an 1/8 of the reserved pages
0,
0);
if (g_pShimHeap == NULL) {
//
// We didn't get our heap.
//
DPF(dlError, "[SeiInitGlobals] Can't create shim heap.\n");
return FALSE;
}
//
// Get the DLL handle for this shim engine
//
#ifdef SE_WIN2K
bResult = SeiGetModuleHandle("Shim.dll", &g_pShimEngModHandle);
#else
bResult = SeiGetModuleHandle("ShimEng.dll", &g_pShimEngModHandle);
#endif // SE_WIN2K
if (!bResult) {
DPF(dlError, "[SeiInitGlobals] Failed to get the shim engine's handle\n");
return FALSE;
}
g_pShimEngLdrEntry = SeiGetLoaderEntry(Peb, g_pShimEngModHandle);
//
// Setup the log file.
//
SeiInitFileLog(lpszFullPath);
g_bInitGlobals = TRUE;
return TRUE;
}
void
SeiLayersCheck(
IN LPCWSTR lpszFullPath,
OUT LPBOOL lpbApplyExes,
OUT LPBOOL lpbApplyToSystemExes,
OUT SDBQUERYRESULT* psdbQuery
)
{
BOOL bLayerEnvSet = FALSE;
BOOL bCmdExe = FALSE;
//
// Get the flags from the environment variable, if any.
//
bLayerEnvSet = SeiCheckLayerEnvVarFlags(lpbApplyExes, lpbApplyToSystemExes);
//
// make sure we are not cmd.exe
//
bCmdExe = (_wcsicmp(lpszFullPath, g_szCmdExePath) == 0);
//
// Unless the environment variable has the flag that allows layer shimming of
// system exes, check for the EXE being in System32 or Windir.
// If it is, disable any layer that is coming from the environment variable,
// and clear the environment variable so the layer won't be propagated.
//
if (bLayerEnvSet && !*lpbApplyToSystemExes) {
if (g_dwSystem32StrLen &&
_wcsnicmp(g_szSystem32, lpszFullPath, g_dwSystem32StrLen) == 0) {
//
// In this case, we'll exclude anything in System32 or any
// subdirectories.
//
DPF(dlWarning,
"[SeiLayersCheck] Won't apply layer to \"%S\" because it is in System32.\n",
lpszFullPath);
psdbQuery->atrLayers[0] = TAGREF_NULL;
if (!bCmdExe) {
SeiClearLayerEnvVar();
}
} else if (!*lpbApplyToSystemExes &&
g_dwWindirStrLen &&
_wcsnicmp(g_szWindir, lpszFullPath, g_dwWindirStrLen) == 0) {
DWORD i;
BOOL bInWindir = TRUE;
//
// The app is somewhere in the windows tree, but we only want to exclude
// the windows directory, not the subdirectories.
//
for (i = g_dwWindirStrLen; lpszFullPath[i] != 0; ++i) {
if (lpszFullPath[i] == L'\\') {
//
// It's in a subdirectory.
//
bInWindir = FALSE;
break;
}
}
if (bInWindir) {
DPF(dlWarning,
"[SeiLayersCheck] Won't apply layer(s) to \"%S\" because"
" it is in Windows dir.\n",
lpszFullPath);
psdbQuery->atrLayers[0] = TAGREF_NULL;
if (!bCmdExe) {
SeiClearLayerEnvVar();
}
}
}
}
}
void
SeiPropagateLayers(
IN HSDB hSDB,
IN SDBQUERYRESULT* psdbQuery,
IN BOOL bApplyExes,
IN BOOL bApplyToSystemExes,
OUT LPDWORD lpdwShimsCount
)
{
int i;
WCHAR szFullEnvVar[MAX_PATH];
TAGREF trShimRef;
//
// Count the DLLs that trLayer uses, and put together the environment variable
//
szFullEnvVar[0] = 0;
//
// Make sure to propagate the flags.
//
if (!bApplyExes) {
wcscat(szFullEnvVar, L"!");
}
if (bApplyToSystemExes) {
wcscat(szFullEnvVar, L"#");
}
for (i = 0; i < SDB_MAX_LAYERS && psdbQuery->atrLayers[i] != TAGREF_NULL; ++i) {
WCHAR* pszEnvVar;
//
// Get the environment var and tack it onto the full string
//
pszEnvVar = SeiGetLayerName(hSDB, psdbQuery->atrLayers[i]);
if (pszEnvVar) {
wcscat(szFullEnvVar, pszEnvVar);
wcscat(szFullEnvVar, L" ");
}
//
// Keep counting the dlls.
//
trShimRef = SdbFindFirstTagRef(hSDB, psdbQuery->atrLayers[i], TAG_SHIM_REF);
while (trShimRef != TAGREF_NULL) {
(*lpdwShimsCount)++;
trShimRef = SdbFindNextTagRef(hSDB, psdbQuery->atrLayers[i], trShimRef);
}
}
//
// Set the layer environment variable if not set
//
if (szFullEnvVar[0] && psdbQuery->atrLayers[0]) {
SeiSetLayerEnvVar(szFullEnvVar);
}
}
BOOL
SeiGetShimCommandLine(
HSDB hSDB,
TAGREF trShimRef,
LPSTR lpszCmdLine
)
{
TAGREF trCmdLine;
WCHAR wszCmdLine[SHIM_COMMAND_LINE_MAX_BUFFER];
//
// Check for command line
//
lpszCmdLine[0] = 0;
trCmdLine = SdbFindFirstTagRef(hSDB, trShimRef, TAG_COMMAND_LINE);
if (trCmdLine == TAGREF_NULL) {
return FALSE;
}
wszCmdLine[0] = 0;
if (SdbReadStringTagRef(hSDB, trCmdLine, wszCmdLine, SHIM_COMMAND_LINE_MAX_BUFFER)) {
UNICODE_STRING UnicodeString;
ANSI_STRING AnsiString = { 0, SHIM_COMMAND_LINE_MAX_BUFFER, lpszCmdLine };
NTSTATUS status;
RtlInitUnicodeString(&UnicodeString, wszCmdLine);
status = RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, FALSE);
//
// If conversion is unsuccessful, reset to zero-length string.
//
if (!NT_SUCCESS(status)) {
lpszCmdLine[0] = 0;
return FALSE;
}
}
return TRUE;
}
#ifndef SE_WIN2K
BOOL
SeiSetApphackFlags(
HSDB hSDB,
SDBQUERYRESULT* psdbQuery
)
{
ULARGE_INTEGER uliKernel;
ULARGE_INTEGER uliUser;
BOOL bUsingApphackFlags = FALSE;
PPEB Peb = NtCurrentPeb();
SdbQueryFlagMask(hSDB, psdbQuery, TAG_FLAG_MASK_KERNEL, &uliKernel.QuadPart, NULL);
SdbQueryFlagMask(hSDB, psdbQuery, TAG_FLAG_MASK_USER, &uliUser.QuadPart, NULL);
Peb->AppCompatFlags.QuadPart = uliKernel.QuadPart;
Peb->AppCompatFlagsUser.QuadPart = uliUser.QuadPart;
if (uliKernel.QuadPart != 0) {
DPF(dlPrint, "[SeiSetApphackFlags] Using kernel apphack flags 0x%x.\n", uliKernel.LowPart);
swprintf(g_szPipeData, L"%s - Using kernel apphack flags 0x%x",
g_szExeName,
uliKernel.LowPart);
SeiSendDataToPipe();
bUsingApphackFlags = TRUE;
}
if (uliUser.QuadPart != 0) {
DPF(dlPrint, "[SeiSetApphackFlags] Using user apphack flags 0x%x.\n", uliUser.LowPart);
swprintf(g_szPipeData, L"%s - Using user apphack flags 0x%x",
g_szExeName,
uliUser.LowPart);
SeiSendDataToPipe();
bUsingApphackFlags = TRUE;
}
return bUsingApphackFlags;
}
#endif
typedef struct tagTRSHIM {
TAGREF trShimRef;
BOOL bPlaceholder;
} TRSHIM, *PTRSHIM;
typedef struct tagTRSHIMARRAY {
int nShimRefCount;
int nShimRefMax;
TRSHIM* parrShimRef;
} TRSHIMARRAY, *PTRSHIMARRAY;
#define TR_DELTA 4
BOOL
SeiAddShim(
IN PTRSHIMARRAY pShimArray,
IN TAGREF trShimRef,
IN BOOL bPlaceholder
)
{
if (pShimArray->nShimRefCount >= pShimArray->nShimRefMax) {
PTRSHIM parrShimRef;
DWORD dwSize;
dwSize = (pShimArray->nShimRefMax + TR_DELTA) * sizeof(TRSHIM);
parrShimRef = (PTRSHIM)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
dwSize);
if (parrShimRef == NULL) {
DPF(dlError, "[SeiAddShim] Failed to allocate %d bytes.\n", dwSize);
return FALSE;
}
memcpy(parrShimRef, pShimArray->parrShimRef, pShimArray->nShimRefMax * sizeof(TRSHIM));
pShimArray->nShimRefMax += TR_DELTA;
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pShimArray->parrShimRef);
pShimArray->parrShimRef = parrShimRef;
}
pShimArray->parrShimRef[pShimArray->nShimRefCount].trShimRef = trShimRef;
pShimArray->parrShimRef[pShimArray->nShimRefCount].bPlaceholder = bPlaceholder;
(pShimArray->nShimRefCount)++;
return TRUE;
}
PTRSHIMARRAY
SeiBuildShimRefArray(
IN HSDB hSDB,
IN SDBQUERYRESULT* psdbQuery,
OUT LPDWORD lpdwShimCount,
IN BOOL bApplyExes,
IN BOOL bApplyToSystemExes,
IN BOOL bIsSetup
)
{
DWORD dw;
TAGREF trExe;
TAGREF trLayer;
TAGREF trShimRef;
DWORD dwShimsCount = 0;
WCHAR szFullEnvVar[MAX_PATH];
PTRSHIMARRAY pShimArray;
*lpdwShimCount = 0;
pShimArray = (PTRSHIMARRAY)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(TRSHIMARRAY));
if (pShimArray == NULL) {
DPF(dlError, "[SeiBuildShimRefArray] Failed to allocate %d bytes.\n", sizeof(TRSHIMARRAY));
return NULL;
}
for (dw = 0; dw < SDB_MAX_EXES; dw++) {
trExe = psdbQuery->atrExes[dw];
if (trExe == TAGREF_NULL) {
break;
}
//
// Count the SHIMs that this EXE uses.
//
trShimRef = SdbFindFirstTagRef(hSDB, trExe, TAG_SHIM_REF);
while (trShimRef != TAGREF_NULL) {
SeiAddShim(pShimArray, trShimRef, FALSE);
dwShimsCount++;
trShimRef = SdbFindNextTagRef(hSDB, trExe, trShimRef);
}
}
//
// Count the DLLs that trLayer uses, and put together the environment variable
//
szFullEnvVar[0] = 0;
//
// Make sure to propagate the flags.
//
if (!bApplyExes) {
wcscat(szFullEnvVar, L"!");
}
if (bApplyToSystemExes) {
wcscat(szFullEnvVar, L"#");
}
for (dw = 0; dw < SDB_MAX_LAYERS && psdbQuery->atrLayers[dw] != TAGREF_NULL; dw++) {
WCHAR* pszEnvVar;
trLayer = psdbQuery->atrLayers[dw];
//
// Get the environment var and tack it onto the full string
//
pszEnvVar = SeiGetLayerName(hSDB, trLayer);
if (bIsSetup && !wcscmp(pszEnvVar, L"LUA")) {
//
// If the user is trying to apply the LUA layer to a setup program,
// we ignore it.
//
continue;
}
if (pszEnvVar) {
wcscat(szFullEnvVar, pszEnvVar);
wcscat(szFullEnvVar, L" ");
}
//
// Keep counting the shims.
//
trShimRef = SdbFindFirstTagRef(hSDB, trLayer, TAG_SHIM_REF);
while (trShimRef != TAGREF_NULL) {
SeiAddShim(pShimArray, trShimRef, FALSE);
dwShimsCount++;
trShimRef = SdbFindNextTagRef(hSDB, trLayer, trShimRef);
}
}
//
// Set the layer environment variable if not set
//
if (szFullEnvVar[0] && psdbQuery->atrLayers[0]) {
SeiSetLayerEnvVar(szFullEnvVar);
}
*lpdwShimCount = dwShimsCount;
return pShimArray;
}
BOOL
SeiIsSetup(
IN LPCWSTR pwszFullPath
)
{
WCHAR wszModuleName[MAX_PATH];
LPWSTR pwszModuleName = NULL;
wcsncpy(wszModuleName, pwszFullPath, MAX_PATH);
wszModuleName[MAX_PATH - 1] = 0;
pwszModuleName = wcsrchr(wszModuleName, L'\\') + 1;
_wcslwr(pwszModuleName);
if (wcsstr(pwszModuleName, L"setup") || wcsstr(pwszModuleName, L"install")) {
return TRUE;
} else {
LPWSTR pwsz;
if (pwsz = wcsstr(pwszModuleName, L"_ins")) {
if (wcsstr(pwsz + 4, L"_mp")) {
return TRUE;
}
}
}
return FALSE;
}
BOOL
SeiInit(
IN LPCWSTR pwszFullPath,
IN HSDB hSDB,
OUT SDBQUERYRESULT* psdbQuery,
IN LPCSTR lpszModuleToShim,
IN BOOL bDynamic
)
/*++
Return: TRUE on success, FALSE otherwise.
Desc: Injects all the shims and patches specified for this EXE
in the database.
--*/
{
PPEB Peb = NtCurrentPeb();
BOOL bResult = FALSE;
TAGREF trShimRef;
NTSTATUS status;
DWORD dwCounter = 0;
WCHAR wszDLLPath[MAX_PATH];
WCHAR wszShimName[MAX_PATH];
CHAR szCmdLine[SHIM_COMMAND_LINE_MAX_BUFFER];
DWORD dwTotalHooks = 0;
BOOL bApplyExes = TRUE;
BOOL bApplyToSystemExes = FALSE;
BOOL bUsingApphackFlags = FALSE;
DWORD dwAPIsHooked = 0;
DWORD dw;
DWORD dwShimsCount = 0;
PHOOKAPI* pHookArray = NULL;
PSHIMINFO pShimInfo;
int nShimRef;
PTRSHIMARRAY pShimArray = NULL;
BOOL bIsSetup;
g_bShimDuringInit = TRUE;
#ifndef SE_WIN2K
if (!bDynamic) {
//
// Mark almost all loaded DLLs as if they already run their init routines.
//
SeiSetEntryProcessed(Peb);
if (psdbQuery->trAppHelp) {
if (!SeiDisplayAppHelp(hSDB, psdbQuery)) {
//
// We should never end up here because SeiDisplayApphelp
// calls TerminateProcess before returning FALSE.
//
goto cleanup;
}
}
}
#endif // SE_WIN2K
bIsSetup = SeiIsSetup(pwszFullPath);
if (!SeiInitGlobals(pwszFullPath)) {
DPF(dlError, "[SeiInit] Failed to initialize global data\n");
goto cleanup;
}
SeiLayersCheck(pwszFullPath, &bApplyExes, &bApplyToSystemExes, psdbQuery);
//
// This should be taken care of by apphelp, but
// we're taking a belt-and-suspenders approach here.
//
if (!bApplyExes) {
psdbQuery->atrExes[0] = TAGREF_NULL;
}
pShimArray = SeiBuildShimRefArray(hSDB,
psdbQuery,
&dwShimsCount,
bApplyExes,
bApplyToSystemExes,
bIsSetup);
if (pShimArray == NULL) {
DPF(dlError, "[SeiInit] Failed to build the shimref array\n");
goto cleanup;
}
//
// Set some global variables so we'll know if we're using a layer,
// an exe entry, or both.
//
// These variables are only used for debug purposes.
//
if (psdbQuery->atrExes[0] != TAGREF_NULL) {
g_bUsingExe = TRUE;
}
if (psdbQuery->atrLayers[0] != TAGREF_NULL) {
g_bUsingLayer = TRUE;
}
//
// Debug spew for matching notification.
//
DPF(dlPrint, "[SeiInit] Matched entry: \"%S\"\n", pwszFullPath);
//
// Send the name of the process to the pipe
//
swprintf(g_szPipeData, L"New process created: %s", pwszFullPath);
SeiSendDataToPipe();
#ifndef SE_WIN2K
//
// Get the apphack flags. These can only be enabled if the shimengine is not
// dynamically initialized.
//
if (!bDynamic && !(bUsingApphackFlags = SeiSetApphackFlags(hSDB, psdbQuery))) {
DPF(dlPrint, "[SeiInit] No apphack flags for this app \"%S\".\n", pwszFullPath);
}
#endif // SE_WIN2K
//
// See if there are any shims.
//
if (dwShimsCount == 0) {
DPF(dlPrint, "[SeiInit] No new SHIMs for this app \"%S\".\n", pwszFullPath);
goto OnlyPatches;
}
//
// We need to load the global inclusion/exclusion list if any.
//
SeiBuildGlobalInclList(hSDB);
if (g_dwShimsCount == 0) {
//
// Increment the shims count to allow for our internal stubs.
// Also reserve space for up to MAX_DYNAMIC_SHIMS more dynamic shims.
//
dwShimsCount++;
g_dwMaxShimsCount = dwShimsCount + MAX_DYNAMIC_SHIMS;
//
// Allocate a storage pointer for the hook information.
//
g_pHookArray = (PHOOKAPI*)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(PHOOKAPI) * g_dwMaxShimsCount);
if (g_pHookArray == NULL) {
DPF(dlError,
"[SeiInit] Failure allocating %d bytes for the hook array\n",
sizeof(PHOOKAPI) * g_dwMaxShimsCount);
goto cleanup;
}
//
// Allocate the array that keeps information about the shims.
//
g_pShimInfo = (PSHIMINFO)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(SHIMINFO) * g_dwMaxShimsCount);
if (g_pShimInfo == NULL) {
DPF(dlError,
"[SeiInit] Failure allocating %d bytes for the SHIMINFO array\n",
sizeof(SHIMINFO) * g_dwMaxShimsCount);
goto cleanup;
}
//
// Point the local variables to the beginning of the arrays.
//
pHookArray = g_pHookArray;
pShimInfo = g_pShimInfo;
} else {
if (g_dwShimsCount + dwShimsCount >= g_dwMaxShimsCount) {
DPF(dlError, "[SeiInit] Too many shims\n");
goto cleanup;
}
//
// Point the local variables to the end of the existing arrays.
//
pHookArray = g_pHookArray + g_dwShimsCount;
pShimInfo = g_pShimInfo + g_dwShimsCount;
}
//
// Get the first shim.
//
nShimRef = 0;
while (nShimRef < pShimArray->nShimRefCount) {
PVOID pModuleHandle = NULL;
UNICODE_STRING UnicodeString;
ANSI_STRING ProcedureNameString;
PFNGETHOOKAPIS pfnGetHookApis = NULL;
TAGREF trShimName = TAGREF_NULL;
LPWSTR pwszDllShortName;
DWORD i, dwShimIndex;
trShimRef = pShimArray->parrShimRef[nShimRef].trShimRef;
//
// Retrieve the shim name.
//
wszShimName[0] = 0;
trShimName = SdbFindFirstTagRef(hSDB, trShimRef, TAG_NAME);
if (trShimName == TAGREF_NULL) {
DPF(dlError, "[SeiInit] Could not retrieve shim name tag from entry.\n");
goto cleanup;
}
if (!SdbReadStringTagRef(hSDB, trShimName, wszShimName, MAX_PATH)) {
DPF(dlError, "[SeiInit] Could not retrieve shim name from entry.\n");
goto cleanup;
}
//
// Check for duplicate shims.
//
for (i = 0; i < g_dwShimsCount + dwCounter; ++i) {
if (_wcsnicmp(g_pShimInfo[i].wszName, wszShimName, MAX_SHIM_NAME_LEN) == 0) {
dwShimIndex = i;
goto nextShim;
}
}
//
// Save off the name of the shim.
//
wcsncpy(pShimInfo[dwCounter].wszName, wszShimName, MAX_SHIM_NAME_LEN);
pShimInfo[dwCounter].wszName[MAX_SHIM_NAME_LEN] = 0;
if (!SdbGetDllPath(hSDB, trShimRef, wszDLLPath)) {
DPF(dlError, "[SeiInit] Failed to get DLL Path\n");
goto cleanup;
}
pwszDllShortName = SeiGetShortName(wszDLLPath);
RtlInitUnicodeString(&UnicodeString, wszDLLPath);
//
// Check if we already loaded this DLL.
//
status = LdrGetDllHandle(NULL,
NULL,
&UnicodeString,
&pModuleHandle);
if (!NT_SUCCESS(status)) {
//
// Load the DLL that hosts this shim.
//
#ifndef SE_WIN2K
//
// Save the name of the DLL that we're about to load so we don't screw
// it's init routine.
//
wcscpy(g_wszShimDllInLoading, pwszDllShortName);
#endif // SE_WIN2K
status = LdrLoadDll(UNICODE_NULL, NULL, &UnicodeString, &pModuleHandle);
if (!NT_SUCCESS(status)) {
DPF(dlError,
"[SeiInit] Failed to load DLL \"%S\" Status 0x%lx\n",
wszDLLPath, status);
goto cleanup;
}
DPF(dlPrint,
"[SeiInit] Shim DLL 0x%X \"%S\" loaded\n",
pModuleHandle,
wszDLLPath);
}
DPF(dlPrint, "[SeiInit] Using SHIM \"%S!%S\"\n",
wszShimName, pwszDllShortName);
pShimInfo[dwCounter].pDllBase = pModuleHandle;
//
// Check for command line.
//
if (SeiGetShimCommandLine(hSDB, trShimRef, szCmdLine)) {
DPF(dlPrint,
"[SeiInit] Command line for Shim \"%S\" : \"%s\"\n",
wszShimName,
szCmdLine);
}
//
// Send this shim name to the pipe
//
swprintf(g_szPipeData,
L"%s - Applying shim %s(%S) from %s",
g_szExeName,
wszShimName,
szCmdLine,
pwszDllShortName);
SeiSendDataToPipe();
//
// Get the GetHookApis entry point.
//
RtlInitString(&ProcedureNameString, "GetHookAPIs");
status = LdrGetProcedureAddress(pModuleHandle,
&ProcedureNameString,
0,
(PVOID*)&pfnGetHookApis);
if (!NT_SUCCESS(status) || pfnGetHookApis == NULL) {
DPF(dlError,
"[SeiInit] Failed to get 'GetHookAPIs' address, DLL \"%S\"\n",
wszDLLPath);
goto cleanup;
}
dwTotalHooks = 0;
//
// Call the proc and then store away its hook params.
//
pHookArray[dwCounter] = (*pfnGetHookApis)(szCmdLine, wszShimName, &dwTotalHooks);
dwAPIsHooked += dwTotalHooks;
DPF(dlInfo,
"[SeiInit] GetHookAPIs returns %d hooks for DLL \"%S\" SHIM \"%S\"\n",
dwTotalHooks, wszDLLPath, wszShimName);
pShimInfo[dwCounter].dwHookedAPIs = dwTotalHooks;
pShimInfo[dwCounter].pLdrEntry = SeiGetLoaderEntry(Peb, pModuleHandle);
if (dwTotalHooks > 0) {
//
// Initialize the HOOKAPIEX structure.
//
for (i = 0; i < dwTotalHooks; ++i) {
PHOOKAPIEX pHookEx;
pHookEx = (PHOOKAPIEX)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
HEAP_ZERO_MEMORY,
sizeof(HOOKAPIEX));
if (!pHookEx) {
DPF(dlError,
"[SeiInit] Failed to allocate %d bytes (HOOKAPIEX)\n",
sizeof(HOOKAPIEX));
goto cleanup;
}
pHookArray[dwCounter][i].pHookEx = pHookEx;
pHookArray[dwCounter][i].pHookEx->dwShimID = g_dwShimsCount + dwCounter;
}
#if DBG
//
// Give a debugger warning if uninitialized HOOKAPI structures
// are used.
//
{
DWORD dwUninitCount = 0;
for (i = 0; i < dwTotalHooks; ++i) {
if (pHookArray[dwCounter][i].pszModule == NULL ||
pHookArray[dwCounter][i].pszFunctionName == NULL) {
dwUninitCount++;
}
}
if (dwUninitCount > 0) {
DPF(dlWarning,
"[SeiInit] Shim \"%S\" using %d uninitialized HOOKAPI structures.\n",
pShimInfo[dwCounter].wszName, dwUninitCount);
}
}
#endif // DBG
}
dwShimIndex = g_dwShimsCount + dwCounter;
dwCounter++;
nextShim:
//
// Read the inclusion/exclusion list for this shim.
//
if (bDynamic && lpszModuleToShim != NULL) {
if (!SeiBuildInclListWithOneModule(dwShimIndex, lpszModuleToShim)) {
DPF(dlError,
"[SeiInit] Couldn't build the inclusion list w/ one module for Shim \"%S\"\n",
wszShimName);
goto cleanup;
}
} else {
if (!SeiBuildInclExclList(hSDB, trShimRef, dwShimIndex, pwszFullPath)) {
DPF(dlError,
"[SeiInit] Couldn't build the inclusion/exclusion list for Shim \"%S\"\n",
wszShimName);
goto cleanup;
}
}
//
// Go to the next shim ref.
//
nShimRef++;
}
if (dwAPIsHooked > 0) {
//
// We need to add our internal hooks
//
if (SeiAddInternalHooks(dwCounter)) {
dwCounter++;
}
}
//
// Update the shim counter.
//
g_dwShimsCount += dwCounter;
OnlyPatches:
for (dw = 0; dw < SDB_MAX_EXES; dw++) {
if (psdbQuery->atrExes[dw] == TAGREF_NULL) {
break;
}
//
// Loop through available in-memory patches for this EXE.
//
SeiLoadPatches(hSDB, psdbQuery->atrExes[dw]);
}
if (g_dwMemoryPatchCount == 0) {
DPF(dlPrint, "[SeiInit] No patches for this app \"%S\".\n", pwszFullPath);
}
if (g_dwMemoryPatchCount == 0 && g_dwShimsCount == 0 && !bUsingApphackFlags) {
DPF(dlError, "[SeiInit] No fixes in the DB for this app \"%S\".\n", pwszFullPath);
goto cleanup;
}
//
// Walk the hook list and fixup available APIs
//
if (!PatchNewModules(bDynamic)) {
DPF(dlError, "[SeiInit] Unsuccessful fixing up APIs, EXE \"%S\"\n", pwszFullPath);
goto cleanup;
}
//
// Notify the shims that static link DLLs have run their init routine.
//
if (bDynamic) {
NotifyShims(SN_STATIC_DLLS_INITIALIZED, 1);
}
//
// The shim has successfuly initialized
//
g_bShimInitialized = TRUE;
bResult = TRUE;
cleanup:
//
// Cleanup
//
if (pShimArray) {
if (pShimArray->parrShimRef) {
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pShimArray->parrShimRef);
}
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pShimArray);
}
if (!bDynamic) {
//
// We can pass back any one of the exes. The first is fine.
//
if (psdbQuery->atrExes[0] != TAGREF_NULL) {
SdbReleaseMatchingExe(hSDB, psdbQuery->atrExes[0]);
}
if (hSDB != NULL) {
SdbReleaseDatabase(hSDB);
}
#ifndef SE_WIN2K
SeiResetEntryProcessed(Peb);
#endif // SE_WIN2K
}
g_bShimDuringInit = FALSE;
if (!bResult) {
#if DBG
if (!bUsingApphackFlags) {
DbgPrint("[SeiInit] Shim engine failed to initialize.\n");
}
#endif // DBG
//
// Unload the shim DLLs that are loaded so far.
//
// Don't do this during dynamic shimming.
//
if (!bDynamic) {
if (g_pShimInfo != NULL) {
for (dwCounter = 0; dwCounter < g_dwShimsCount; dwCounter++) {
if (g_pShimInfo[dwCounter].pDllBase == NULL) {
break;
}
LdrUnloadDll(g_pShimInfo[dwCounter].pDllBase);
}
}
if (g_pShimHeap != NULL) {
RtlDestroyHeap(g_pShimHeap);
g_pShimHeap = NULL;
}
}
}
return bResult;
}
HSDB
SeiGetShimData(
IN PPEB Peb,
IN PVOID pShimData,
OUT LPWSTR pwszFullPath, // this is supplied on Whistler and returned on Win2k
OUT SDBQUERYRESULT* psdbQuery
)
{
HSDB hSDB = NULL;
BOOL bResult;
#ifdef DEBUG_SPEW
SeiInitDebugSupport();
#endif // DEBUG_SPEW
//
// Get the name of the executable being run.
//
if (!SeiGetExeName(Peb, pwszFullPath)) {
DPF(dlError, "[SeiGetShimData] Can't get EXE name\n");
return NULL;
}
if (!_wcsicmp(g_szExeName, L"ntsd.exe") ||
!_wcsicmp(g_szExeName, L"windbg.exe")) {
DPF(dlPrint, "[SeiGetShimData] not shimming ntsd.exe\n");
return NULL;
}
//
// Open up the Database and see if there's any blob information about this EXE.
//
hSDB = SdbInitDatabase(0, NULL);
if (hSDB == NULL) {
DPF(dlError, "[SeiGetShimData] Can't open shim DB.\n");
return NULL;
}
//
// Ensure that the sdbQuery starts out clean.
//
ZeroMemory(psdbQuery, sizeof(SDBQUERYRESULT));
#ifdef SE_WIN2K
bResult = SdbGetMatchingExe(hSDB, pwszFullPath, NULL, NULL, 0, psdbQuery);
#else
bResult = SdbUnpackAppCompatData(hSDB, pwszFullPath, pShimData, psdbQuery);
#endif // SE_WIN2K
if (!bResult) {
DPF(dlError, "[SeiGetShimData] Can't get EXE data\n");
goto failure;
}
return hSDB;
failure:
SdbReleaseDatabase(hSDB);
return NULL;
}
#ifdef SE_WIN2K
BOOL
LoadPatchDll(
IN LPCSTR pszCmdLine // The command line from the registry.
// Unused parameter.
)
/*++
Return: TRUE on success, FALSE otherwise. However user32.dll ignores the return
value of this function.
Desc: This function is called from user32.dll to initialize the shim engine.
It queries the shim database and loads all the shim DLLs and patches that
are available for this EXE.
--*/
{
PPEB Peb = NtCurrentPeb();
WCHAR wszFullPath[MAX_PATH];
HSDB hSDB;
SDBQUERYRESULT sdbQuery;
hSDB = SeiGetShimData(Peb, NULL, wszFullPath, &sdbQuery);
if (hSDB == NULL) {
DPF(dlError, "[LoadPatchDll] Failed to get shim data\n");
return FALSE;
}
return SeiInit(wszFullPath, hSDB, &sdbQuery, NULL, FALSE);
}
#else
BOOL
SeiDisplayAppHelp(
IN HSDB hSDB,
IN PSDBQUERYRESULT pSdbQuery
)
/*++
Return: void
Desc: This function launches apphelp for the starting EXE.
--*/
{
PDB pdb;
TAGID tiExe;
GUID guidDB;
WCHAR wszGuid[MAX_PATH];
WCHAR wszCommandLine[MAX_PATH];
DWORD dwExit;
char szName[50];
PVOID hModule, pAddress;
WCHAR wszTemp[MAX_PATH];
UNICODE_STRING ustrTemp;
UNICODE_STRING ustrGuid;
ANSI_STRING strName;
STARTUPINFOW StartupInfo;
PROCESS_INFORMATION ProcessInfo;
//
// If we have any errors along the way, go ahead and run the app.
//
if (!SdbTagRefToTagID(hSDB, pSdbQuery->trAppHelp, &pdb, &tiExe)) {
DPF(dlError, "[SeiDisplayAppHelp] Failed to convert tagref to tagid.\n");
return TRUE;
}
if (!SdbGetDatabaseGUID(hSDB, pdb, &guidDB)) {
DPF(dlError, "[SeiDisplayAppHelp] Failed to get DB guid.\n");
return TRUE;
}
ustrGuid.Buffer = wszGuid;
ustrGuid.Length = 0;
ustrGuid.MaximumLength = MAX_PATH;
if (RtlStringFromGUID(&guidDB, &ustrGuid) != STATUS_SUCCESS) {
DPF(dlError, "[SeiDisplayAppHelp] Failed to convert guid to string.\n");
return TRUE;
}
swprintf(wszCommandLine, L"ahui.exe %s 0x%x", ustrGuid.Buffer, tiExe);
//
// We need a hack here to get kernel32.dll to initialize. We load aclayers.dll
// to trigger the init routine of kernel32.
//
SdbpGetAppPatchDir(wszTemp);
wcscat(wszTemp, L"\\aclayers.dll");
RtlInitUnicodeString(&ustrTemp, wszTemp);
//
// Save the name of the DLL that we're about to load so we don't screw
// it's init routine.
//
wcscpy(g_wszShimDllInLoading, L"aclayers.dll");
LdrLoadDll(UNICODE_NULL, NULL, &ustrTemp, &hModule);
g_hApphelpDllHelper = hModule;
RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
StartupInfo.cb = sizeof(StartupInfo);
if (!CreateProcessW(NULL, wszCommandLine, NULL, NULL, FALSE, 0, NULL, NULL,
&StartupInfo, &ProcessInfo)) {
DPF(dlError, "[SeiDisplayAppHelp] Failed to launch apphelp process.\n");
return TRUE;
}
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
GetExitCodeProcess(ProcessInfo.hProcess, &dwExit);
if (!dwExit) {
SeiResetEntryProcessed(NtCurrentPeb());
TerminateProcess(GetCurrentProcess(), 0);
return FALSE;
}
return TRUE;
}
#ifdef SE_WIN2K
#define SeiCheckComPlusImage(Peb)
#else
void
SeiCheckComPlusImage(
PPEB Peb
)
{
PIMAGE_NT_HEADERS NtHeader;
ULONG Cor20HeaderSize;
NtHeader = RtlImageNtHeader(Peb->ImageBaseAddress);
g_bComPlusImage = FALSE;
g_bComPlusImage = (RtlImageDirectoryEntryToData(Peb->ImageBaseAddress,
TRUE,
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR,
&Cor20HeaderSize) != NULL);
DPF(dlPrint, "[SeiCheckComPlusImage] COM+ executable %s\n",
(g_bComPlusImage ? "TRUE" : "FALSE"));
}
#endif // SE_WIN2K
void
SE_InstallBeforeInit(
IN PUNICODE_STRING pstrFullPath, // The name of the starting EXE
IN PVOID pShimExeData // The pointer provided by apphelp.dll
)
/*++
Return: void
Desc: This function installs the shim support for the starting EXE.
--*/
{
PPEB Peb = NtCurrentPeb();
HSDB hSDB;
SDBQUERYRESULT sdbQuery;
hSDB = SeiGetShimData(Peb, pShimExeData, pstrFullPath->Buffer, &sdbQuery);
if (hSDB == NULL) {
DPF(dlError, "[SE_InstallBeforeInit] Failed to get shim data\n");
return;
}
//
// Check if the image is a COM+ image
//
SeiCheckComPlusImage(Peb);
SeiInit(pstrFullPath->Buffer, hSDB, &sdbQuery, NULL, FALSE);
if (pShimExeData != NULL) {
SIZE_T dwSize;
dwSize = SdbGetAppCompatDataSize(pShimExeData);
if (dwSize > 0) {
NtFreeVirtualMemory(NtCurrentProcess(),
&pShimExeData,
&dwSize,
MEM_RELEASE);
}
}
return;
}
#endif // SE_WIN2K
#ifndef SE_WIN2K
void
SE_ProcessDying(
void
)
{
NotifyShims(SN_PROCESS_DYING, 0);
return;
}
#endif // SE_WIN2K
BOOL
SE_DynamicShim(
IN LPCWSTR lpszFullPath,
IN HSDB hSDB,
IN SDBQUERYRESULT* psdbQuery,
IN LPCSTR lpszModuleToShim
)
/*++
Return: TRUE on success, FALSE otherwise.
Desc: This function attempts to inject shims dynamically.
--*/
{
SeiInitDebugSupport();
if (lpszModuleToShim != NULL && *lpszModuleToShim == 0) {
lpszModuleToShim = NULL;
}
SeiInit(lpszFullPath, hSDB, psdbQuery, lpszModuleToShim, TRUE);
#ifndef SE_WIN2K
LdrInitShimEngineDynamic(g_hModule);
#endif
return TRUE;
}
BOOL
SeiSendDataToPipe(
void
)
{
OBJECT_ATTRIBUTES ObjA;
FILE_PIPE_INFORMATION fpi;
UNICODE_STRING uName;
IO_STATUS_BLOCK ioStatusBlock;
HANDLE hPipe;
NTSTATUS status;
LARGE_INTEGER liOffset;
ULONG uBytes;
DWORD dwRet, dwCount;
RtlInitUnicodeString(&uName, PIPE_NAME);
InitializeObjectAttributes(&ObjA,
&uName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
ioStatusBlock.Status = 0;
ioStatusBlock.Information = 0;
//
// Open the named pipe
//
status = NtCreateFile(&hPipe,
FILE_GENERIC_WRITE,
&ObjA,
&ioStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,
0,
NULL,
0);
if ((!NT_SUCCESS(status) || INVALID_HANDLE_VALUE == hPipe)) {
dwRet = RtlNtStatusToDosError(status);
DPF(dlError,
"[SeiSendDataToPipe] Failed to open named pipe. Error code: %d\n",
dwRet);
return FALSE;
}
//
// Change the mode of the named pipe to message mode
//
fpi.ReadMode = FILE_PIPE_MESSAGE_MODE;
fpi.CompletionMode = FILE_PIPE_QUEUE_OPERATION;
status = NtSetInformationFile(hPipe,
&ioStatusBlock,
&fpi,
sizeof(FILE_PIPE_INFORMATION),
FilePipeInformation);
if (!NT_SUCCESS(status)) {
dwRet = RtlNtStatusToDosError(status);
DPF(dlError,
"[SeiSendDataToPipe] Failed to change handle state. Error code: %d\n",
dwRet);
return FALSE;
}
ioStatusBlock.Status = 0;
ioStatusBlock.Information = 0;
liOffset.LowPart = 0;
liOffset.HighPart = 0;
//
// Send the data to the named pipe
//
uBytes = wcslen(g_szPipeData) * sizeof(WCHAR);
status = NtWriteFile(hPipe,
NULL,
NULL,
NULL,
&ioStatusBlock,
(PVOID)g_szPipeData,
uBytes,
&liOffset,
NULL);
if (!NT_SUCCESS(status)) {
dwRet = RtlNtStatusToDosError(status);
DPF(dlError,
"[SeiSendDataToPipe] Failed to send data. Error code: %d\n",
dwRet);
NtClose(hPipe);
return FALSE;
}
NtClose(hPipe);
return TRUE;
}
BOOL WINAPI
DllMain(
HINSTANCE hInstance,
DWORD dwreason,
LPVOID reserved
)
{
//
// The init routine for DLL_PROCESS_ATTACH will NEVER be called because
// ntdll calls LdrpLoadDll for this shim engine w/o calling the init routine.
// Look in ntdll\ldrinit.c LdrpLoadShimEngine.
// The only case when we will have hInstance is when we are loaded dynamically
//
if (dwreason == DLL_PROCESS_ATTACH) {
g_hModule = (HMODULE)hInstance;
}
return TRUE;
UNREFERENCED_PARAMETER(reserved);
}