You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6457 lines
187 KiB
6457 lines
187 KiB
/*++
|
|
|
|
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>
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4201 4214)
|
|
#include <winbase.h>
|
|
#include <stdio.h>
|
|
#include <apcompat.h>
|
|
#include "shimdb.h"
|
|
#pragma warning(pop)
|
|
|
|
#include "ShimEng.h"
|
|
#include <sfcfiles.h>
|
|
|
|
#define NEW_QSORT_NAME shimeng_qsort
|
|
#include "_qsort.h"
|
|
|
|
#ifdef SE_WIN2K
|
|
#include "NotifyCallback.h"
|
|
#endif // SE_WIN2K
|
|
|
|
#define STRSAFE_NO_CB_FUNCTIONS
|
|
#include <strsafe.h>
|
|
|
|
#pragma warning(disable:4054 4055 4152 4201 4204 4214 4221 4706)
|
|
|
|
#ifndef SE_WIN2K
|
|
extern BOOL
|
|
LdrInitShimEngineDynamic(
|
|
PVOID pShimengModule
|
|
);
|
|
#endif
|
|
|
|
|
|
//
|
|
// 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
|
|
|
|
typedef struct tagINEXMOD {
|
|
char* pszModule;
|
|
struct tagINEXMOD* pNext;
|
|
} INEXMOD, *PINEXMOD;
|
|
|
|
typedef enum tagINEX_MODE {
|
|
INEX_UNINITIALIZED = 0,
|
|
EXCLUDE_SYSTEM32,
|
|
EXCLUDE_SYSTEM32_SFP, // exclude all SFPed files in system32/winsxs.
|
|
EXCLUDE_ALL,
|
|
EXCLUDE_ALL_EXCEPT_NONSFP, // exclude all except the SFPed files in system32/winsxs.
|
|
INCLUDE_ALL
|
|
} INEX_MODE, *PINEX_MODE;
|
|
|
|
#define MAX_SHIM_NAME_LEN 64
|
|
|
|
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]; // name of shim
|
|
DWORD dwDynamicToken;
|
|
} SHIMINFO, *PSHIMINFO;
|
|
|
|
typedef struct tagNTVDMTASK {
|
|
|
|
LIST_ENTRY entry;
|
|
ULONG uTask; // 16bit task ID. (NTVDM shimming only)
|
|
DWORD dwShimsCount;
|
|
DWORD dwMaxShimsCount;
|
|
SHIMINFO* pShimInfo; // the shim info associated with this task.
|
|
PHOOKAPI* pHookArray; // the hooked api array associated with this task.
|
|
|
|
} NTVDMTASK, *PNTVDMTASK;
|
|
|
|
LIST_ENTRY g_listNTVDMTasks;
|
|
|
|
#define MAX_MOD_LEN 128
|
|
|
|
typedef enum tagSYSTEMDLL_MODE {
|
|
NOT_SYSTEMDLL = 0, // the dll is not in system32 or winsxs.
|
|
SYSTEMDLL_SYSTEM32, // the dll is in system32.
|
|
SYSTEMDLL_WINSXS // the dll is in winsxs - SfcIsFileProteced will always
|
|
// return TRUE if the dll is in winsxs.
|
|
} SYSTEMDLL_MODE, *PSYSTEMDLL_MODE;
|
|
|
|
typedef struct tagHOOKEDMODULE {
|
|
PVOID pDllBase; // the base address of the loaded module
|
|
ULONG ulSizeOfImage; // the size of the DLL image
|
|
char szModuleName[MAX_MOD_LEN]; // the name of the loaded module
|
|
SYSTEMDLL_MODE eSystemDllMode; // is this dll a system dll?
|
|
|
|
} 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);
|
|
|
|
// SfcGetFiles exported by sfcfiles.dll.
|
|
typedef NTSTATUS (*PFNSFCGETFILES)(PPROTECT_FILE_ENTRY *pFiles, ULONG* pulFileCount);
|
|
|
|
BOOL
|
|
SeiDbgPrint(
|
|
void
|
|
);
|
|
|
|
#ifdef SE_WIN2K
|
|
|
|
BOOL PatchNewModules(
|
|
BOOL bDynamic
|
|
);
|
|
|
|
#else
|
|
|
|
void
|
|
SeiDisplayAppHelp(
|
|
HSDB hSDB,
|
|
PSDBQUERYRESULT pSdbQuery
|
|
);
|
|
|
|
#endif // SE_WIN2K
|
|
|
|
BOOL
|
|
SeiUnhookImports(
|
|
IN PBYTE pDllBase,
|
|
IN LPCSTR pszDllName,
|
|
IN BOOL bRevertPfnOld
|
|
);
|
|
|
|
BOOL
|
|
SE_DynamicUnshim(
|
|
IN DWORD dwDynamicToken
|
|
);
|
|
|
|
//
|
|
// 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;
|
|
|
|
// HACK ALERT! See SeiInit for the explanation what this means.
|
|
BOOL g_bHookAllGetProcAddress = FALSE;
|
|
|
|
// 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];
|
|
|
|
// The internal HOOKEX array for the hooks that shimeng adds.
|
|
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;
|
|
|
|
// Each time SE_DynamicShim is called, we return a token which we use to
|
|
// determine which shims to remove when SE_DynamicUnshim is called, unless
|
|
// it's the shimming one module case when we track the token internally
|
|
// in shimeng.
|
|
typedef struct tagDYNAMICTOKEN {
|
|
|
|
BYTE bToken;
|
|
LPSTR pszModule;
|
|
|
|
} DYNAMICTOKEN, *PDYNAMICTOKEN;
|
|
|
|
DYNAMICTOKEN g_DynamicTokens[MAX_DYNAMIC_SHIMS];
|
|
|
|
// 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;
|
|
|
|
// This is TRUE only when we are inside of SeiInit doing all the patching work.
|
|
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 apppatch directory
|
|
WCHAR g_szAppPatch[MAX_PATH] = L"";
|
|
|
|
// The length of the apppatch directory string;
|
|
DWORD g_dwAppPatchStrLen = 0;
|
|
|
|
BOOL g_bWow64 = FALSE;
|
|
|
|
// The syswow64 directory, only used when g_bWow64 is TRUE.
|
|
// Notice it's the same length of the system32 dir so we don't
|
|
// store the length separately.
|
|
LPWSTR g_pwszSyswow64 = NULL;
|
|
|
|
// 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;
|
|
|
|
// This is the spew we send to shimviewer.
|
|
WCHAR g_wszFullShimViewerData[SHIMVIEWER_DATA_SIZE + SHIMVIEWER_DATA_PREFIX_LEN];
|
|
LPWSTR g_pwszShimViewerData;
|
|
|
|
// This tells if the engine is applied to NTVDM
|
|
BOOL g_bNTVDM = FALSE;
|
|
|
|
#define SYSTEM32_DIR L"%systemroot%\\system32\\"
|
|
#define SYSTEM32_DIR_LEN (sizeof(SYSTEM32_DIR)/sizeof(WCHAR) - 1)
|
|
|
|
PPROTECT_FILE_ENTRY g_pAllSFPedFiles;
|
|
LPCWSTR* g_pwszSFPedFileNames;
|
|
DWORD g_dwSFPedFileNames;
|
|
HMODULE g_hModSfcFiles;
|
|
|
|
RTL_CRITICAL_SECTION g_csEng;
|
|
|
|
|
|
#ifndef SE_WIN2K
|
|
|
|
// The name of the shim DLL that the engine is just about to load.
|
|
WCHAR g_wszShimDllInLoading[MAX_MOD_LEN];
|
|
|
|
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
|
|
|
|
static WCHAR s_wszSystem32[] = L"system32\\";
|
|
static WCHAR s_wszSysWow64[] = L"syswow64\\";
|
|
|
|
#ifdef DEBUG_SPEW
|
|
|
|
BOOL g_bDbgPrintEnabled;
|
|
DEBUGLEVEL g_DebugLevel;
|
|
|
|
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);
|
|
|
|
//
|
|
// Reserve one character for the potential '\n' that we may be adding.
|
|
//
|
|
StringCchVPrintfA(szT, 1024 - 1, pszFmt, arglist);
|
|
|
|
va_end(arglist);
|
|
|
|
//
|
|
// Make sure we have a '\n' at the end of the string
|
|
//
|
|
len = (int)strlen(szT);
|
|
|
|
if (len > 0 && szT[len - 1] != '\n') {
|
|
szT[len] = '\n';
|
|
szT[len + 1] = 0;
|
|
}
|
|
|
|
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;
|
|
|
|
default:
|
|
DbgPrint("[XXXX] ");
|
|
break;
|
|
}
|
|
|
|
DbgPrint("%s", szT);
|
|
}
|
|
}
|
|
|
|
void
|
|
SeiInitDebugSupport(
|
|
void
|
|
)
|
|
/*++
|
|
Return: void
|
|
|
|
Desc: This function initializes g_bDbgPrintEnabled based on an env variable on
|
|
a chk build. On fre builds we still have the debug code but the env variable
|
|
is ignored. You can change the debug level via the debugger extension:
|
|
!shimexts.debuglevel
|
|
--*/
|
|
{
|
|
#if DBG
|
|
|
|
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 // DBG
|
|
}
|
|
#else
|
|
|
|
#define SeiInitDebugSupport()
|
|
|
|
#endif // DEBUG_SPEW
|
|
|
|
int __cdecl
|
|
SeiSFPedFileNameCompare(
|
|
const void* pElement1,
|
|
const void* pElement2
|
|
)
|
|
{
|
|
LPCWSTR pwszName1 = *(LPCWSTR*)pElement1;
|
|
LPCWSTR pwszName2 = *(LPCWSTR*)pElement2;
|
|
|
|
return _wcsicmp(pwszName1, pwszName2);
|
|
}
|
|
|
|
BOOL
|
|
SeiGetSFPedFiles()
|
|
{
|
|
PFNSFCGETFILES pfnSfcGetFiles = NULL;
|
|
ULONG ulAllSFPedFiles = 0;
|
|
DWORD dw;
|
|
NTSTATUS status;
|
|
LPCWSTR pwszFileName, pwszFileNameStart;
|
|
BOOL bInitSFPedFiles = FALSE;
|
|
|
|
if (g_hModSfcFiles == NULL) {
|
|
g_hModSfcFiles = LoadLibrary("sfcfiles.dll");
|
|
|
|
if (g_hModSfcFiles == NULL) {
|
|
DPF(dlError, "[SeiGetSFPedFiles] Failed to load sfcfiles.dll\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
pfnSfcGetFiles = (PFNSFCGETFILES)GetProcAddress(g_hModSfcFiles, "SfcGetFiles");
|
|
|
|
if (pfnSfcGetFiles == NULL) {
|
|
DPF(dlError,
|
|
"[SeiGetSFPedFiles] Failed to get the proc address of SfcGetFiles\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
status = (*pfnSfcGetFiles)(&g_pAllSFPedFiles, &ulAllSFPedFiles);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
DPF(dlError,
|
|
"[SeiGetSFPedFiles] Failed to get the SFPed files - Status 0x%lx\n",
|
|
status);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We only care about the files in system32 and since there are no
|
|
// duplicated file names in system32 (so far, anyway), we just
|
|
// build an array that points to the beginning of the file names in
|
|
// system32.
|
|
//
|
|
g_pwszSFPedFileNames =
|
|
(LPCWSTR*)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
ulAllSFPedFiles * sizeof(LPCWSTR));
|
|
|
|
if (g_pwszSFPedFileNames == NULL) {
|
|
DPF(dlError,
|
|
"[SeiGetSFPedFiles] Failed to allocate %d bytes\n",
|
|
ulAllSFPedFiles * sizeof(LPCWSTR));
|
|
goto cleanup;
|
|
}
|
|
|
|
for (dw = 0; dw < ulAllSFPedFiles; dw++) {
|
|
|
|
if (!_wcsnicmp(g_pAllSFPedFiles[dw].FileName,
|
|
SYSTEM32_DIR,
|
|
SYSTEM32_DIR_LEN)) {
|
|
|
|
pwszFileName = g_pAllSFPedFiles[dw].FileName;
|
|
pwszFileNameStart = wcsrchr(pwszFileName, L'\\');
|
|
|
|
if (pwszFileNameStart) {
|
|
g_pwszSFPedFileNames[g_dwSFPedFileNames++] = ++pwszFileNameStart;
|
|
} else {
|
|
g_pwszSFPedFileNames[g_dwSFPedFileNames++] = pwszFileName;
|
|
}
|
|
}
|
|
}
|
|
|
|
qsort((void*)g_pwszSFPedFileNames,
|
|
g_dwSFPedFileNames,
|
|
sizeof(LPCWSTR),
|
|
&SeiSFPedFileNameCompare);
|
|
|
|
bInitSFPedFiles = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if (!bInitSFPedFiles) {
|
|
FreeLibrary(g_hModSfcFiles);
|
|
g_pwszSFPedFileNames = NULL;
|
|
g_dwSFPedFileNames = 0;
|
|
}
|
|
|
|
return bInitSFPedFiles;
|
|
}
|
|
|
|
BOOL
|
|
SeiIsSFPed(
|
|
IN LPCSTR pszModule,
|
|
OUT BOOL* pbIsSFPed
|
|
)
|
|
/*++
|
|
Return: TRUE if we could get the SFP info, FALSE otherwise.
|
|
|
|
Desc: Check if pszModule is SFPed.
|
|
--*/
|
|
{
|
|
int iMid, iLeft, iRight, iCompare;
|
|
ANSI_STRING AnsiString;
|
|
UNICODE_STRING UnicodeString;
|
|
WCHAR wszBuffer[MAX_PATH];
|
|
|
|
if (g_dwSFPedFileNames == 0) {
|
|
|
|
if (!SeiGetSFPedFiles()) {
|
|
DPF(dlError,
|
|
"[SeiIsSFPed] We don't have info on the SFPed files!\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
RtlInitAnsiString(&AnsiString, pszModule);
|
|
|
|
UnicodeString.Buffer = wszBuffer;
|
|
UnicodeString.MaximumLength = sizeof(wszBuffer);
|
|
|
|
if (!NT_SUCCESS(RtlAnsiStringToUnicodeString(&UnicodeString,
|
|
&AnsiString,
|
|
FALSE))){
|
|
|
|
DPF(dlError,
|
|
"[SeiIsSFPedA] Failed to convert string \"%s\" to UNICODE.\n",
|
|
pszModule);
|
|
return FALSE;
|
|
}
|
|
|
|
*pbIsSFPed = FALSE;
|
|
iLeft = 0;
|
|
iRight = g_dwSFPedFileNames - 1;
|
|
iMid = iRight / 2;
|
|
|
|
while (iLeft <= iRight) {
|
|
|
|
iCompare = _wcsicmp(wszBuffer, g_pwszSFPedFileNames[iMid]);
|
|
|
|
if (iCompare == 0) {
|
|
*pbIsSFPed = TRUE;
|
|
break;
|
|
} else if (iCompare < 0) {
|
|
iRight = iMid - 1;
|
|
} else {
|
|
iLeft = iMid + 1;
|
|
}
|
|
|
|
iMid = (iLeft + iRight) / 2;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PHOOKAPI
|
|
SeiConstructChain(
|
|
IN PNTVDMTASK pNTVDMTask, // if we are in ntvdm this is the task info.
|
|
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;
|
|
PHOOKAPI* pHookArray = NULL;
|
|
PSHIMINFO pShimInfo = NULL;
|
|
DWORD dwShimsCount = 0;
|
|
|
|
*pdwDllIndex = 0;
|
|
|
|
if (pNTVDMTask == NULL) {
|
|
pHookArray = g_pHookArray;
|
|
pShimInfo = g_pShimInfo;
|
|
dwShimsCount = g_dwShimsCount;
|
|
} else {
|
|
pHookArray = pNTVDMTask->pHookArray;
|
|
pShimInfo = pNTVDMTask->pShimInfo;
|
|
dwShimsCount = pNTVDMTask->dwShimsCount;
|
|
}
|
|
|
|
//
|
|
// Scan all HOOKAPI entries for corresponding function pointer.
|
|
//
|
|
for (i = (LONG)dwShimsCount - 1; i >= 0; i--) {
|
|
|
|
for (j = 0; j < pShimInfo[i].dwHookedAPIs; j++) {
|
|
|
|
if (pHookArray[i][j].pfnOld == pfnOld) {
|
|
|
|
if (pTopHookAPI != NULL) {
|
|
|
|
//
|
|
// The chain has already begun, so tack this one on
|
|
// to the end.
|
|
//
|
|
pBottomHookAPI->pfnOld = pHookArray[i][j].pfnNew;
|
|
if (pBottomHookAPI->pHookEx) {
|
|
pBottomHookAPI->pHookEx->pNext = &(pHookArray[i][j]);
|
|
}
|
|
|
|
pBottomHookAPI = &(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 (pHookArray[i][j].pHookEx && pHookArray[i][j].pHookEx->pTopOfChain) {
|
|
|
|
//
|
|
// Chain has already been constructed.
|
|
//
|
|
return pHookArray[i][j].pHookEx->pTopOfChain;
|
|
}
|
|
|
|
//
|
|
// Not hooked yet. Set to top of chain.
|
|
//
|
|
pTopHookAPI = &(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);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Can't have the same API hooked multiple times
|
|
// by the same shim.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
//
|
|
wcsncpy(wszModule, pRelAddress->moduleName, MAX_PATH);
|
|
wszModule[MAX_PATH - 1] = 0;
|
|
|
|
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;
|
|
|
|
//
|
|
// SECURITY: this needs to be done under a try/except
|
|
//
|
|
|
|
//
|
|
// Grab the opcode and see what we have to do.
|
|
//
|
|
for (;;) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (g_pwszShimViewerData) {
|
|
StringCchPrintfW(g_pwszShimViewerData,
|
|
SHIMVIEWER_DATA_SIZE,
|
|
L"%s - Applied %d of %d patches",
|
|
g_szExeName,
|
|
dwSucceeded,
|
|
g_dwMemoryPatchCount);
|
|
|
|
SeiDbgPrint();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
SeiResolveAPIs(
|
|
IN PNTVDMTASK pNTVDMTask
|
|
)
|
|
/*++
|
|
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;
|
|
PHOOKAPI* pHookArray = NULL;
|
|
PSHIMINFO pShimInfo = NULL;
|
|
DWORD dwShimsCount = 0;
|
|
|
|
if (pNTVDMTask == NULL) {
|
|
pHookArray = g_pHookArray;
|
|
pShimInfo = g_pShimInfo;
|
|
dwShimsCount = g_dwShimsCount;
|
|
} else {
|
|
pHookArray = pNTVDMTask->pHookArray;
|
|
pShimInfo = pNTVDMTask->pShimInfo;
|
|
dwShimsCount = pNTVDMTask->dwShimsCount;
|
|
}
|
|
|
|
UnicodeString.Buffer = wszBuffer;
|
|
|
|
for (i = 0; i < dwShimsCount; i++) {
|
|
|
|
//
|
|
// See if we've already resolved all the APIs this shim DLL wanted to hook.
|
|
//
|
|
if (pShimInfo[i].dwFlags & SIF_RESOLVED) {
|
|
continue;
|
|
}
|
|
|
|
bAllApisResolved = TRUE;
|
|
|
|
for (j = 0; j < pShimInfo[i].dwHookedAPIs; j++) {
|
|
//
|
|
// Ignore resolved APIs.
|
|
//
|
|
if (pHookArray[i][j].pfnOld != NULL) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Don't try to load unspecified modules.
|
|
//
|
|
if (pHookArray[i][j].pszModule == NULL) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Is this DLL mapped in the address space?
|
|
//
|
|
RtlInitAnsiString(&AnsiString, 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 = 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;
|
|
}
|
|
|
|
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) {
|
|
pShimInfo[i].dwFlags |= SIF_RESOLVED;
|
|
}
|
|
}
|
|
}
|
|
|
|
__inline BOOL
|
|
SeiGetSFPInfoOnDemand(
|
|
IN LPCSTR pszModule,
|
|
IN BOOL bIsInWinSXS // is this module in winsxs or system32?
|
|
)
|
|
{
|
|
BOOL bShouldExclude, bIsSFPed;
|
|
|
|
if (bIsInWinSXS) {
|
|
|
|
//
|
|
// SfcIsFileProtected claims that DLLs in winsxs are all SFPed.
|
|
//
|
|
bShouldExclude = TRUE;
|
|
} else {
|
|
|
|
//
|
|
// If we failed to determine if this file is SFPed, we revert to the
|
|
// old behavior - exclude it.
|
|
//
|
|
bShouldExclude = (!SeiIsSFPed(pszModule, &bIsSFPed) ? TRUE : bIsSFPed);
|
|
}
|
|
|
|
return bShouldExclude;
|
|
}
|
|
|
|
BOOL
|
|
SeiIsExcluded(
|
|
IN LPCSTR pszModule, // the module to test for exclusion.
|
|
IN PHOOKAPI pTopHookAPI, // the HOOKAPI for which we test for exclusion.
|
|
IN SYSTEMDLL_MODE eSystemDllMode // 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;
|
|
INEX_MODE eInExMode;
|
|
|
|
//
|
|
// 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;
|
|
eInExMode = g_pShimInfo[dwCounter].eInExMode;
|
|
|
|
switch (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:
|
|
case EXCLUDE_SYSTEM32_SFP:
|
|
{
|
|
//
|
|
// 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 (eSystemDllMode != NOT_SYSTEMDLL) {
|
|
|
|
BOOL bShouldExclude;
|
|
|
|
if (eInExMode == EXCLUDE_SYSTEM32) {
|
|
bShouldExclude = TRUE;
|
|
} else {
|
|
|
|
bShouldExclude =
|
|
SeiGetSFPInfoOnDemand(pszModule,
|
|
(eSystemDllMode == SYSTEMDLL_WINSXS));
|
|
}
|
|
|
|
if (bShouldExclude) {
|
|
|
|
if (eInExMode == EXCLUDE_SYSTEM32) {
|
|
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
|
|
DPF(dlInfo,
|
|
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!#%d\", "
|
|
"because it is in System32/WinSXS.\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/WinSXS.\n",
|
|
pszModule,
|
|
g_pShimInfo[dwCounter].wszName,
|
|
pTopHookAPI->pszModule,
|
|
pTopHookAPI->pszFunctionName);
|
|
}
|
|
} else {
|
|
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
|
|
DPF(dlInfo,
|
|
"[SeiIsExcluded] module \"%s\" excluded for shim %S, API \"%s!#%d\", "
|
|
"because it is in System32/WinSXS and is SFPed.\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/WinSXS and is SFPed.\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:
|
|
case EXCLUDE_ALL_EXCEPT_NONSFP:
|
|
{
|
|
//
|
|
// 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 (eSystemDllMode != NOT_SYSTEMDLL && eInExMode == EXCLUDE_ALL_EXCEPT_NONSFP) {
|
|
|
|
//
|
|
// If we have
|
|
// <EXCLUDE MODULE="*"/>
|
|
// <INCLUDE MODULE="NOSFP"/>
|
|
// we need to include the files in system32 that are not SFPed.
|
|
//
|
|
if (!SeiGetSFPInfoOnDemand(pszModule,
|
|
(eSystemDllMode == SYSTEMDLL_WINSXS))) {
|
|
//
|
|
// If the module is not SFPed we need to include this shim.
|
|
//
|
|
bExclude = FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
PVOID
|
|
SeiGetOriginalImport(
|
|
PVOID pfn
|
|
)
|
|
{
|
|
DWORD i, j;
|
|
PHOOKAPI pHook;
|
|
|
|
for (i = 0; i < g_dwShimsCount; i++) {
|
|
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
|
|
|
|
if (g_pHookArray[i][j].pfnNew == pfn) {
|
|
|
|
//
|
|
// Go to the end of the chain and find the original import.
|
|
//
|
|
pHook = &g_pHookArray[i][j];
|
|
while (pHook && pHook->pHookEx && pHook->pHookEx->pNext) {
|
|
pHook = pHook->pHookEx->pNext;
|
|
}
|
|
|
|
if (pHook) {
|
|
return (pHook->pfnOld);
|
|
} else {
|
|
//
|
|
// We shouldn't get here - this is just to satisfy prefix.
|
|
//
|
|
ASSERT(pHook);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
return pfn;
|
|
}
|
|
|
|
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 SYSTEMDLL_MODE eSystemDllMode, // is this dll a system DLL?
|
|
IN BOOL bDynamic,
|
|
IN BOOL bAddNewEntry
|
|
)
|
|
/*++
|
|
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_MOD_LEN] = "";
|
|
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;
|
|
|
|
//
|
|
// Make sure we're not hooking more DLLs than we can.
|
|
//
|
|
if (g_dwHookedModuleCount == SHIM_MAX_HOOKED_MODULES) {
|
|
DPF(dlError, "[SeiHookImports] Too many modules hooked!!!\n");
|
|
ASSERT(g_dwHookedModuleCount == SHIM_MAX_HOOKED_MODULES - 1);
|
|
return FALSE;
|
|
}
|
|
|
|
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 (pIID != NULL) {
|
|
|
|
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);
|
|
|
|
for (;;) {
|
|
|
|
SIZE_T dwFuncAddr;
|
|
|
|
pfnOld = (PVOID)pITDA->u1.Function;
|
|
|
|
//
|
|
// Done with all the imports from this module? (terminating condition)
|
|
//
|
|
if (pITDA->u1.Ordinal == 0) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// In the dynamic shimming case we need to get the original function pointer
|
|
// because the one we just got could already be shimmed.
|
|
//
|
|
if (bDynamic) {
|
|
pfnOld = SeiGetOriginalImport(pfnOld);
|
|
}
|
|
|
|
pTopHookAPI = SeiConstructChain(0, pfnOld, &i);
|
|
|
|
if (pTopHookAPI == NULL ||
|
|
SeiIsExcluded(szBaseDllName, pTopHookAPI, eSystemDllMode)) {
|
|
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++;
|
|
}
|
|
|
|
if (bAddNewEntry) {
|
|
//
|
|
// 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].eSystemDllMode = eSystemDllMode;
|
|
|
|
StringCchCopyA(g_hHookedModules[g_dwHookedModuleCount++].szModuleName,
|
|
MAX_MOD_LEN,
|
|
szBaseDllName);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
SeiHookNTVDM(
|
|
IN LPCWSTR pwszModule, // the 16 bit app for which we hook APIs
|
|
IN OUT PVDMTABLE pVDMTable, // the table to hook
|
|
IN PNTVDMTASK pNTVDMTask
|
|
)
|
|
/*++
|
|
Return: TRUE if successful.
|
|
|
|
Desc: Walks the import table of the specified module and patches the APIs
|
|
that need to be hooked.
|
|
--*/
|
|
{
|
|
PHOOKAPI pTopHookAPI;
|
|
PVOID pfnOld;
|
|
int nMod;
|
|
DWORD dwIndex;
|
|
|
|
DPF(dlInfo, "[SeiHookNTVDM] Hooking table for module \"%S\"\n", pwszModule);
|
|
|
|
//
|
|
// Loop through the VDM table and search for the APIs that we want to patch
|
|
//
|
|
for (nMod = 0; nMod < pVDMTable->nApiCount; nMod++) {
|
|
|
|
pfnOld = pVDMTable->ppfnOrig[nMod];
|
|
|
|
pTopHookAPI = SeiConstructChain(pNTVDMTask, pfnOld, &dwIndex);
|
|
|
|
if (pTopHookAPI == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if ((ULONG_PTR)pTopHookAPI->pszFunctionName < 0x0000FFFF) {
|
|
DPF(dlInfo,
|
|
"[SeiHookNTVDM] Hooking API \"%s!#%d\"\n",
|
|
pTopHookAPI->pszModule,
|
|
pTopHookAPI->pszFunctionName);
|
|
} else {
|
|
DPF(dlInfo,
|
|
"[SeiHookNTVDM] Hooking API \"%s!%s\"\n",
|
|
pTopHookAPI->pszModule,
|
|
pTopHookAPI->pszFunctionName);
|
|
}
|
|
|
|
//
|
|
// Hook this API
|
|
//
|
|
pVDMTable->ppfnOrig[nMod] = pTopHookAPI->pfnNew;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
SYSTEMDLL_MODE eSystemDllMode;
|
|
BOOL bIsNewEntry;
|
|
|
|
ASSERT(!g_bNTVDM);
|
|
|
|
//
|
|
// Resolve any APIs that became available from newly loaded modules.
|
|
//
|
|
SeiResolveAPIs(NULL);
|
|
|
|
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!
|
|
//
|
|
if (g_dwAppPatchStrLen &&
|
|
(_wcsnicmp(g_szAppPatch,
|
|
LdrEntry->FullDllName.Buffer,
|
|
g_dwAppPatchStrLen) == 0)) {
|
|
|
|
goto Continue;
|
|
}
|
|
|
|
//
|
|
// Don't hook the shim engine!
|
|
//
|
|
if (LdrEntry->DllBase == g_pShimEngModHandle) {
|
|
goto Continue;
|
|
}
|
|
|
|
//
|
|
// Do nothing if it's already hooked.
|
|
//
|
|
for (i = 0; i < g_dwHookedModuleCount; i++) {
|
|
if (LdrEntry->DllBase == g_hHookedModules[i].pDllBase) {
|
|
|
|
if (bDynamic) {
|
|
|
|
//
|
|
// We need to repatch the IATs in the dynamic shimming
|
|
// case.
|
|
//
|
|
break;
|
|
} else {
|
|
goto Continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
bIsNewEntry = (i == g_dwHookedModuleCount);
|
|
|
|
if (bIsNewEntry) {
|
|
//
|
|
// Check if this DLL is in System32 (or WinSxS), and hence a possible candidate for blanket
|
|
// exclusion. Note that when a 32-bit module running under wow64 the FullDllName
|
|
// could be in system32 even though the module is loaded from syswow64 so we need to
|
|
// specifically check for that case.
|
|
//
|
|
if ((g_dwSystem32StrLen &&
|
|
(_wcsnicmp(g_szSystem32,
|
|
LdrEntry->FullDllName.Buffer,
|
|
g_dwSystem32StrLen) == 0) ||
|
|
(g_bWow64 && g_pwszSyswow64 &&
|
|
_wcsnicmp(g_pwszSyswow64,
|
|
LdrEntry->FullDllName.Buffer,
|
|
g_dwSystem32StrLen) == 0))) {
|
|
|
|
eSystemDllMode = SYSTEMDLL_SYSTEM32;
|
|
|
|
} else if ((g_dwSxSStrLen && _wcsnicmp(g_szSxS,
|
|
LdrEntry->FullDllName.Buffer, g_dwSxSStrLen) == 0)) {
|
|
|
|
eSystemDllMode = SYSTEMDLL_WINSXS;
|
|
|
|
} else {
|
|
eSystemDllMode = NOT_SYSTEMDLL;
|
|
}
|
|
} else {
|
|
eSystemDllMode = g_hHookedModules[i].eSystemDllMode;
|
|
}
|
|
|
|
//
|
|
// This is a candidate for hooking.
|
|
//
|
|
// BUGBUG: shouldn't we check the return value?
|
|
SeiHookImports(LdrEntry->DllBase,
|
|
LdrEntry->SizeOfImage,
|
|
&LdrEntry->BaseDllName,
|
|
eSystemDllMode,
|
|
bDynamic,
|
|
bIsNewEntry);
|
|
|
|
Continue:
|
|
LdrNext = LdrEntry->InMemoryOrderLinks.Flink;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
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_MOD_LEN];
|
|
CHAR szModule[MAX_MOD_LEN];
|
|
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 TRUE;
|
|
}
|
|
|
|
trDatabase = SdbFindFirstTagRef(hSDB, TAGID_ROOT, TAG_DATABASE);
|
|
|
|
if (trDatabase == TAGREF_NULL) {
|
|
DPF(dlError, "[SeiBuildGlobalInclList] Corrupt database. TAG_DATABASE\n");
|
|
ASSERT(trDatabase != TAGREF_NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
trLibrary = SdbFindFirstTagRef(hSDB, trDatabase, TAG_LIBRARY);
|
|
|
|
if (trLibrary == TAGREF_NULL) {
|
|
DPF(dlError, "[SeiBuildGlobalInclList] Corrupt database. TAG_LIBRARY\n");
|
|
ASSERT(trLibrary != TAGREF_NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
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 TRUE;
|
|
}
|
|
|
|
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 FALSE;
|
|
}
|
|
|
|
if (!SdbReadStringTagRef(hSDB, trModule, wszModule, MAX_MOD_LEN)) {
|
|
DPF(dlError,
|
|
"[SeiBuildGlobalInclList] Corrupt database. Inclusion list w/ bad module\n");
|
|
ASSERT(0);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// 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 FALSE;
|
|
}
|
|
|
|
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 FALSE;
|
|
}
|
|
|
|
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 FALSE;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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_MOD_LEN];
|
|
CHAR szModule[MAX_MOD_LEN];
|
|
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_MOD_LEN)) {
|
|
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 (!_wcsicmp(wszModule, L"NOSFP")) {
|
|
|
|
if (bInclude) {
|
|
|
|
//
|
|
// If we see <INCLUDE MODULE="NOSFP"/>, it means we should include the
|
|
// modules in system32 that are not system protected.
|
|
//
|
|
if (g_pShimInfo[dwCounter].eInExMode == EXCLUDE_ALL) {
|
|
g_pShimInfo[dwCounter].eInExMode = EXCLUDE_ALL_EXCEPT_NONSFP;
|
|
} else if (g_pShimInfo[dwCounter].eInExMode == EXCLUDE_SYSTEM32) {
|
|
g_pShimInfo[dwCounter].eInExMode = EXCLUDE_SYSTEM32_SFP;
|
|
}
|
|
} else {
|
|
|
|
DPF(dlInfo,
|
|
"[SeiBuildInclExclListForShim] Specified <EXCLUDE MODULE=\"NOSFP\" - ignored\n",
|
|
wszModule);
|
|
}
|
|
|
|
} 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--;
|
|
}
|
|
|
|
StringCchCopyW(wszModule, MAX_MOD_LEN, 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 = (int)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 = NULL;
|
|
|
|
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);
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, g_pMemoryPatches[g_dwMemoryPatchCount]);
|
|
return;
|
|
}
|
|
|
|
g_dwMemoryPatchCount++;
|
|
|
|
//
|
|
// Get the next patch.
|
|
//
|
|
trPatchRef = SdbFindNextTagRef(hSDB, trExe, trPatchRef);
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
SeiGetModuleHandle(
|
|
IN LPWSTR pwszModule,
|
|
OUT PVOID* pModuleHandle
|
|
)
|
|
/*++
|
|
Return: void
|
|
|
|
Desc: This function loops through the loaded modules and gets the
|
|
handle of the specified named module.
|
|
--*/
|
|
{
|
|
UNICODE_STRING UnicodeString;
|
|
NTSTATUS status;
|
|
|
|
RtlInitUnicodeString(&UnicodeString, pwszModule);
|
|
|
|
status = LdrGetDllHandle(NULL,
|
|
NULL,
|
|
&UnicodeString,
|
|
pModuleHandle);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
DPF(dlError,
|
|
"[SeiGetModuleHandle] Failed to get the handle for \"%S\".\n",
|
|
pwszModule);
|
|
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,
|
|
PSYSTEMDLL_MODE peSystemDllMode
|
|
)
|
|
{
|
|
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.
|
|
//
|
|
StringCchCopyA(pszModuleName, MAX_MOD_LEN, g_hHookedModules[i].szModuleName);
|
|
|
|
*peSystemDllMode = g_hHookedModules[i].eSystemDllMode;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#if defined(_X86_)
|
|
#pragma optimize( "y", off )
|
|
#endif
|
|
|
|
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_MOD_LEN];
|
|
SYSTEMDLL_MODE eSystemDllMode;
|
|
|
|
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(0, pfn, &dwDllIndex);
|
|
|
|
if (pTopHookAPI == NULL) {
|
|
DPF(dlError,
|
|
"[StubGetProcAddress] failed to construct the chain for pfn 0x%p\n",
|
|
pfn);
|
|
return pfn;
|
|
}
|
|
|
|
//
|
|
// HACK ALERT! See SeiInit for the explanation what this means.
|
|
//
|
|
if (!g_bHookAllGetProcAddress) {
|
|
|
|
RtlCaptureStackBackTrace(1, 1, &retAddress, &ulHash);
|
|
|
|
DPF(dlPrint,
|
|
"[StubGetProcAddress] Stack capture caller 0x%p\n",
|
|
retAddress);
|
|
|
|
if (retAddress && SeiGetModuleByAddress(retAddress, szBaseDllName, &eSystemDllMode)) {
|
|
|
|
if (SeiIsExcluded(szBaseDllName, pTopHookAPI, eSystemDllMode)) {
|
|
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;
|
|
}
|
|
|
|
#if defined(_X86_)
|
|
#pragma optimize( "y", on )
|
|
#endif
|
|
|
|
#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[MAX_MOD_LEN] = "";
|
|
|
|
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;
|
|
}
|
|
|
|
StringCchCopyA(szBaseDllName, 128, 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;
|
|
StringCchCopyA(g_hHookedModules[j].szModuleName, MAX_MOD_LEN, "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;
|
|
ULONG uBytes;
|
|
LPSTR pszHeader = NULL;
|
|
DWORD dwHeaderLen;
|
|
char szFormatHeader[] = "-------------------------------------------\r\n"
|
|
" Log \"%S\"\r\n"
|
|
"-------------------------------------------\r\n";
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
HRESULT hr;
|
|
|
|
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,
|
|
NULL)) {
|
|
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;
|
|
|
|
//
|
|
// The header is real simple so we calculate the approximate (slightly bigger)
|
|
// length here.
|
|
//
|
|
dwHeaderLen = sizeof(szFormatHeader) * (DWORD)wcslen(pwszAppName);
|
|
|
|
pszHeader = (LPSTR)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
dwHeaderLen);
|
|
|
|
if (!pszHeader) {
|
|
DPF(dlError,
|
|
"[SeiInitFileLog] Failed to allocate %d bytes for the log header\n",
|
|
dwHeaderLen);
|
|
goto cleanup;
|
|
}
|
|
|
|
hr = StringCchPrintfA(pszHeader, dwHeaderLen, szFormatHeader, pwszAppName);
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
uBytes = (ULONG)strlen(pszHeader);
|
|
|
|
status = NtWriteFile(hfile,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&ioStatusBlock,
|
|
(PVOID)pszHeader,
|
|
uBytes,
|
|
&liOffset,
|
|
NULL);
|
|
}
|
|
|
|
cleanup:
|
|
|
|
if (pszHeader) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pszHeader);
|
|
}
|
|
|
|
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) || pdb == NULL) {
|
|
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;
|
|
g_pShimInfo[dwCounter].dwDynamicToken = 0;
|
|
|
|
StringCchCopyW(g_pShimInfo[dwCounter].wszName, MAX_SHIM_NAME_LEN, 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);
|
|
|
|
StringCchCopyW(pwszExeName, MAX_PATH, Entry->FullDllName.Buffer);
|
|
|
|
//
|
|
// Save the exe name in our global
|
|
//
|
|
StringCchCopyW(g_szExeName, MAX_PATH, 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;
|
|
}
|
|
|
|
ASSERT(!g_bNTVDM);
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
ASSERT(!g_bNTVDM);
|
|
|
|
//
|
|
// 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_DllLoaded(
|
|
PLDR_DATA_TABLE_ENTRY LdrEntry
|
|
)
|
|
{
|
|
if (g_bNTVDM) {
|
|
return;
|
|
}
|
|
|
|
//NotifyShims(SN_DLL_LOADING, (UINT_PTR)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;
|
|
}
|
|
|
|
|
|
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, dwTokenIndex;
|
|
CHAR szBaseDllName[MAX_MOD_LEN] = "";
|
|
ANSI_STRING AnsiString = { 0, sizeof(szBaseDllName), szBaseDllName };
|
|
LPSTR pszModule;
|
|
|
|
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);
|
|
|
|
pszModule = g_hHookedModules[i].szModuleName;
|
|
|
|
//
|
|
// Check if this module dynamically brought in any shims. If so we remove those
|
|
// shims.
|
|
//
|
|
for (dwTokenIndex = 0; dwTokenIndex < MAX_DYNAMIC_SHIMS; dwTokenIndex++) {
|
|
|
|
if (g_DynamicTokens[dwTokenIndex].bToken &&
|
|
g_DynamicTokens[dwTokenIndex].pszModule &&
|
|
(_stricmp(g_DynamicTokens[dwTokenIndex].pszModule, pszModule) == 0)) {
|
|
|
|
SE_DynamicUnshim(dwTokenIndex);
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
StringCchCopyA(g_hHookedModules[j].szModuleName, MAX_MOD_LEN, "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) {
|
|
//
|
|
// Cleanup the crit sec here
|
|
//
|
|
RtlDeleteCriticalSection(&g_csEng);
|
|
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 bRet = FALSE;
|
|
|
|
if (g_bInitGlobals) {
|
|
return TRUE;
|
|
}
|
|
|
|
IsWow64Process(GetCurrentProcess(), &g_bWow64);
|
|
|
|
//
|
|
// Nab the system32 and windows directory path and length.
|
|
//
|
|
StringCchCopyW(g_szWindir, MAX_PATH, USER_SHARED_DATA->NtSystemRoot);
|
|
g_dwWindirStrLen = (DWORD)wcslen(g_szWindir);
|
|
|
|
if (g_szWindir[g_dwWindirStrLen - 1] != L'\\') {
|
|
StringCchCatW(g_szWindir, MAX_PATH, L"\\");
|
|
++g_dwWindirStrLen;
|
|
}
|
|
|
|
StringCchCopyW(g_szSystem32, MAX_PATH, g_szWindir);
|
|
StringCchCatW(g_szSystem32, MAX_PATH, s_wszSystem32);
|
|
g_dwSystem32StrLen = (DWORD)wcslen(g_szSystem32);
|
|
|
|
StringCchCopyW(g_szAppPatch, MAX_PATH, g_szWindir);
|
|
StringCchCatW(g_szAppPatch, MAX_PATH, L"AppPatch\\");
|
|
g_dwAppPatchStrLen = (DWORD)wcslen(g_szAppPatch);
|
|
|
|
StringCchCopyW(g_szSxS, MAX_PATH, g_szWindir);
|
|
StringCchCatW(g_szSxS, MAX_PATH, L"WinSxS\\");
|
|
g_dwSxSStrLen = (DWORD)wcslen(g_szSxS);
|
|
|
|
StringCchCopyW(g_szCmdExePath, MAX_PATH, g_szSystem32);
|
|
StringCchCatW(g_szCmdExePath, MAX_PATH, 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");
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If we are a 32-bit process running on a 64-bit platform, we need to get
|
|
// the syswow64 path.
|
|
//
|
|
if (g_bWow64) {
|
|
g_pwszSyswow64 = (LPWSTR)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(WCHAR) * (g_dwSystem32StrLen + 1));
|
|
|
|
if (!g_pwszSyswow64) {
|
|
DPF(dlError,
|
|
"[SeiInitGlobals] Failed to allocate %d bytes for the syswow64 directory\n",
|
|
sizeof(WCHAR) * (g_dwSystem32StrLen + 1));
|
|
goto cleanup;
|
|
}
|
|
|
|
StringCchCopyW(g_pwszSyswow64, MAX_PATH, g_szWindir);
|
|
StringCchCatW(g_pwszSyswow64, MAX_PATH, s_wszSysWow64);
|
|
}
|
|
|
|
//
|
|
// Get the DLL handle for this shim engine
|
|
//
|
|
#ifdef SE_WIN2K
|
|
bResult = SeiGetModuleHandle(L"Shim.dll", &g_pShimEngModHandle);
|
|
#else
|
|
bResult = SeiGetModuleHandle(L"ShimEng.dll", &g_pShimEngModHandle);
|
|
#endif // SE_WIN2K
|
|
|
|
if (!bResult) {
|
|
DPF(dlError, "[SeiInitGlobals] Failed to get the shim engine's handle\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
g_pShimEngLdrEntry = SeiGetLoaderEntry(Peb, g_pShimEngModHandle);
|
|
|
|
if (g_pShimEngLdrEntry) {
|
|
g_hModule = g_pShimEngLdrEntry->DllBase;
|
|
}
|
|
|
|
//
|
|
// Setup the log file.
|
|
//
|
|
SeiInitFileLog(lpszFullPath);
|
|
|
|
//
|
|
// Check if we need to send debug spew to shimviewer.
|
|
//
|
|
if (g_eShimViewerOption == SHIMVIEWER_OPTION_UNINITIAZED) {
|
|
|
|
SdbGetShowDebugInfoOption();
|
|
|
|
if (g_eShimViewerOption == SHIMVIEWER_OPTION_YES) {
|
|
|
|
ZeroMemory(g_wszFullShimViewerData, sizeof(g_wszFullShimViewerData));
|
|
|
|
StringCchCopyW(g_wszFullShimViewerData,
|
|
SHIMVIEWER_DATA_SIZE + SHIMVIEWER_DATA_PREFIX_LEN,
|
|
SHIMVIEWER_DATA_PREFIX);
|
|
|
|
g_pwszShimViewerData = g_wszFullShimViewerData + SHIMVIEWER_DATA_PREFIX_LEN;
|
|
}
|
|
}
|
|
|
|
g_bInitGlobals = TRUE;
|
|
|
|
bRet = TRUE;
|
|
|
|
cleanup:
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
//
|
|
// The layer flags overwrite the environment variable.
|
|
//
|
|
if (psdbQuery->dwLayerFlags & LAYER_USE_NO_EXE_ENTRIES) {
|
|
*lpbApplyExes = TRUE;
|
|
}
|
|
|
|
if (psdbQuery->dwLayerFlags & LAYER_APPLY_TO_SYSTEM_EXES) {
|
|
*lpbApplyToSystemExes = TRUE;
|
|
}
|
|
|
|
//
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
SeiGetShimCommandLine(
|
|
HSDB hSDB,
|
|
TAGREF trShimRef,
|
|
LPSTR lpszCmdLine
|
|
)
|
|
{
|
|
TAGREF trCmdLine;
|
|
LPWSTR pwszCmdLine = NULL;
|
|
BOOL bRet = FALSE;
|
|
|
|
pwszCmdLine = (LPWSTR)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(WCHAR) * SHIM_COMMAND_LINE_MAX_BUFFER);
|
|
|
|
if (!pwszCmdLine) {
|
|
DPF(dlError,
|
|
"[SeiGetShimCommandLine] Failed to allocate %d bytes for the shim command line\n",
|
|
sizeof(WCHAR) * SHIM_COMMAND_LINE_MAX_BUFFER);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check for command line
|
|
//
|
|
lpszCmdLine[0] = 0;
|
|
|
|
trCmdLine = SdbFindFirstTagRef(hSDB, trShimRef, TAG_COMMAND_LINE);
|
|
|
|
if (trCmdLine == TAGREF_NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
*pwszCmdLine = 0;
|
|
|
|
if (SdbReadStringTagRef(hSDB, trCmdLine, pwszCmdLine, SHIM_COMMAND_LINE_MAX_BUFFER)) {
|
|
|
|
UNICODE_STRING UnicodeString;
|
|
ANSI_STRING AnsiString = { 0, SHIM_COMMAND_LINE_MAX_BUFFER, lpszCmdLine };
|
|
NTSTATUS status;
|
|
|
|
RtlInitUnicodeString(&UnicodeString, pwszCmdLine);
|
|
|
|
status = RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, FALSE);
|
|
|
|
//
|
|
// If conversion is unsuccessful, reset to zero-length string.
|
|
//
|
|
if (!NT_SUCCESS(status)) {
|
|
lpszCmdLine[0] = 0;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
bRet = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if (pwszCmdLine) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pwszCmdLine);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
#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);
|
|
|
|
if (g_pwszShimViewerData) {
|
|
StringCchPrintfW(g_pwszShimViewerData,
|
|
SHIMVIEWER_DATA_SIZE,
|
|
L"%s - Using kernel apphack flags 0x%x",
|
|
g_szExeName,
|
|
uliKernel.LowPart);
|
|
|
|
SeiDbgPrint();
|
|
}
|
|
|
|
bUsingApphackFlags = TRUE;
|
|
}
|
|
|
|
if (uliUser.QuadPart != 0) {
|
|
DPF(dlPrint, "[SeiSetApphackFlags] Using user apphack flags 0x%x.\n", uliUser.LowPart);
|
|
|
|
if (g_pwszShimViewerData) {
|
|
StringCchPrintfW(g_pwszShimViewerData,
|
|
SHIMVIEWER_DATA_SIZE,
|
|
L"%s - Using user apphack flags 0x%x",
|
|
g_szExeName,
|
|
uliUser.LowPart);
|
|
|
|
SeiDbgPrint();
|
|
}
|
|
|
|
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) {
|
|
|
|
if (!SeiAddShim(pShimArray, trShimRef, FALSE)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
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) {
|
|
StringCchCatW(szFullEnvVar, MAX_PATH, L"!");
|
|
}
|
|
|
|
if (bApplyToSystemExes) {
|
|
StringCchCatW(szFullEnvVar, MAX_PATH, 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 && pszEnvVar && !wcscmp(pszEnvVar, L"LUA")) {
|
|
|
|
//
|
|
// If the user is trying to apply the LUA layer to a setup program,
|
|
// we ignore it.
|
|
//
|
|
continue;
|
|
}
|
|
|
|
if (pszEnvVar) {
|
|
StringCchCatW(szFullEnvVar, MAX_PATH, pszEnvVar);
|
|
StringCchCatW(szFullEnvVar, MAX_PATH, L" ");
|
|
}
|
|
|
|
//
|
|
// Keep counting the shims.
|
|
//
|
|
trShimRef = SdbFindFirstTagRef(hSDB, trLayer, TAG_SHIM_REF);
|
|
|
|
while (trShimRef != TAGREF_NULL) {
|
|
|
|
if (!SeiAddShim(pShimArray, trShimRef, FALSE)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
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;
|
|
|
|
cleanup:
|
|
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pShimArray);
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
DWORD
|
|
SeiGetNextDynamicToken(
|
|
void
|
|
)
|
|
{
|
|
DWORD i;
|
|
|
|
//
|
|
// we use the index of the array as the magic number. 0 is reserved
|
|
// for static shims so we start the number from index 1.
|
|
//
|
|
for (i = 1; i < MAX_DYNAMIC_SHIMS; i++) {
|
|
if (g_DynamicTokens[i].bToken == 0) {
|
|
|
|
g_DynamicTokens[i].bToken = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
BOOL
|
|
SeiInit(
|
|
IN LPCWSTR pwszFullPath,
|
|
IN HSDB hSDB,
|
|
IN SDBQUERYRESULT* psdbQuery,
|
|
IN LPCSTR lpszModuleToShim,
|
|
IN BOOL bDynamic,
|
|
OUT LPDWORD lpdwDynamicToken
|
|
)
|
|
/*++
|
|
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;
|
|
LPWSTR pwszDLLPath = NULL;
|
|
WCHAR wszShimName[MAX_SHIM_NAME_LEN];
|
|
LPSTR pszCmdLine = NULL;
|
|
DWORD dwTotalHooks = 0;
|
|
BOOL bApplyExes = TRUE;
|
|
BOOL bApplyToSystemExes = FALSE;
|
|
BOOL bUsingApphackFlags = FALSE;
|
|
DWORD dwAPIsHooked = 0;
|
|
DWORD dw;
|
|
DWORD dwShimsCount = 0;
|
|
DWORD dwDynamicToken = 0;
|
|
PHOOKAPI* pHookArray = NULL;
|
|
PSHIMINFO pShimInfo;
|
|
int nShimRef;
|
|
PTRSHIMARRAY pShimArray = NULL;
|
|
BOOL bIsSetup;
|
|
|
|
g_bShimDuringInit = TRUE;
|
|
|
|
if (bDynamic) {
|
|
dwDynamicToken = SeiGetNextDynamicToken();
|
|
}
|
|
|
|
#ifndef SE_WIN2K
|
|
if (!bDynamic) {
|
|
//
|
|
// Mark almost all loaded DLLs as if they already run their init routines.
|
|
//
|
|
SeiSetEntryProcessed(Peb);
|
|
|
|
if (psdbQuery->trAppHelp) {
|
|
SeiDisplayAppHelp(hSDB, psdbQuery);
|
|
}
|
|
}
|
|
|
|
#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);
|
|
|
|
if (g_pwszShimViewerData) {
|
|
//
|
|
// Send the name of the process to the pipe
|
|
//
|
|
StringCchPrintfW(g_pwszShimViewerData, SHIMVIEWER_DATA_SIZE, L"New process created: %s", pwszFullPath);
|
|
SeiDbgPrint();
|
|
}
|
|
|
|
#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.
|
|
//
|
|
if (!SeiBuildGlobalInclList(hSDB)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
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;
|
|
|
|
pwszDLLPath = (LPWSTR)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(WCHAR) * MAX_PATH);
|
|
|
|
if (!pwszDLLPath) {
|
|
DPF(dlError,
|
|
"[SeiInit] Failed to allocate %d bytes for the dll path\n",
|
|
sizeof(WCHAR) * MAX_PATH);
|
|
goto cleanup;
|
|
}
|
|
|
|
pszCmdLine = (LPSTR)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(CHAR) * SHIM_COMMAND_LINE_MAX_BUFFER);
|
|
|
|
if (!pszCmdLine) {
|
|
DPF(dlError,
|
|
"[SeiInit] Failed to allocate %d bytes for the shim command line\n",
|
|
sizeof(CHAR) * SHIM_COMMAND_LINE_MAX_BUFFER);
|
|
goto cleanup;
|
|
}
|
|
|
|
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_SHIM_NAME_LEN)) {
|
|
DPF(dlError, "[SeiInit] Could not retrieve shim name from entry.\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check for duplicate shims unless it's dynamic shimming. Shims brought
|
|
// in by the dynamic shimming scenario have a different in/exclude
|
|
// list which we need to account for.
|
|
//
|
|
if (!bDynamic) {
|
|
for (i = 0; i < g_dwShimsCount + dwCounter; ++i) {
|
|
if (_wcsnicmp(g_pShimInfo[i].wszName, wszShimName, MAX_SHIM_NAME_LEN - 1) == 0) {
|
|
dwShimIndex = i;
|
|
goto nextShim;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Save off the name of the shim.
|
|
//
|
|
StringCchCopyW(pShimInfo[dwCounter].wszName, MAX_SHIM_NAME_LEN, wszShimName);
|
|
|
|
//
|
|
// HACK ALERT!
|
|
// For StubGetProcAddress we used to not check include/exclude list at all,
|
|
// which means the GetProcAddress calls from all modules are shimmed. Then
|
|
// we added code to take include/exclude list into consideration and that
|
|
// "broke" apps that used to rely on the previous behavior. To compensate
|
|
// for this, we allow you to specify a special shim called
|
|
// "pGetProcAddrExOverride".
|
|
//
|
|
if (_wcsicmp(wszShimName, L"pGetProcAddrExOverride") == 0) {
|
|
g_bHookAllGetProcAddress = TRUE;
|
|
nShimRef++;
|
|
continue;
|
|
}
|
|
|
|
if (!SdbGetDllPath(hSDB, trShimRef, pwszDLLPath, MAX_PATH)) {
|
|
DPF(dlError, "[SeiInit] Failed to get DLL Path\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
pwszDllShortName = SeiGetShortName(pwszDLLPath);
|
|
|
|
RtlInitUnicodeString(&UnicodeString, pwszDLLPath);
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
StringCchCopyW(g_wszShimDllInLoading, MAX_MOD_LEN, 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",
|
|
pwszDLLPath, status);
|
|
goto cleanup;
|
|
}
|
|
|
|
DPF(dlPrint,
|
|
"[SeiInit] Shim DLL 0x%X \"%S\" loaded\n",
|
|
pModuleHandle,
|
|
pwszDLLPath);
|
|
}
|
|
|
|
DPF(dlPrint, "[SeiInit] Using SHIM \"%S!%S\"\n",
|
|
wszShimName, pwszDllShortName);
|
|
|
|
pShimInfo[dwCounter].pDllBase = pModuleHandle;
|
|
|
|
//
|
|
// Check for command line.
|
|
//
|
|
if (SeiGetShimCommandLine(hSDB, trShimRef, pszCmdLine)) {
|
|
DPF(dlPrint,
|
|
"[SeiInit] Command line for Shim \"%S\" : \"%s\"\n",
|
|
wszShimName,
|
|
pszCmdLine);
|
|
}
|
|
|
|
if (g_pwszShimViewerData) {
|
|
//
|
|
// Send this shim name to the pipe
|
|
//
|
|
StringCchPrintfW(g_pwszShimViewerData,
|
|
SHIMVIEWER_DATA_SIZE,
|
|
L"%s - Applying shim %s(%S) from %s",
|
|
g_szExeName,
|
|
wszShimName,
|
|
pszCmdLine,
|
|
pwszDllShortName);
|
|
|
|
SeiDbgPrint();
|
|
}
|
|
|
|
//
|
|
// 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",
|
|
pwszDLLPath);
|
|
goto cleanup;
|
|
}
|
|
|
|
dwTotalHooks = 0;
|
|
|
|
//
|
|
// Call the proc and then store away its hook params.
|
|
//
|
|
pHookArray[dwCounter] = (*pfnGetHookApis)(pszCmdLine, wszShimName, &dwTotalHooks);
|
|
|
|
dwAPIsHooked += dwTotalHooks;
|
|
|
|
DPF(dlInfo,
|
|
"[SeiInit] GetHookAPIs returns %d hooks for DLL \"%S\" SHIM \"%S\"\n",
|
|
dwTotalHooks, pwszDLLPath, wszShimName);
|
|
|
|
pShimInfo[dwCounter].dwHookedAPIs = dwTotalHooks;
|
|
pShimInfo[dwCounter].pLdrEntry = SeiGetLoaderEntry(Peb, pModuleHandle);
|
|
pShimInfo[dwCounter].dwDynamicToken = dwDynamicToken;
|
|
|
|
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) {
|
|
|
|
//
|
|
// We need to record this module name so when it gets unloaded we can
|
|
// remove all the shims this module brought in.
|
|
//
|
|
DWORD dwModuleNameLen = (DWORD)strlen(lpszModuleToShim) + 1;
|
|
LPSTR pszModule =
|
|
(char*)(*g_pfnRtlAllocateHeap)(g_pShimHeap, 0, dwModuleNameLen);
|
|
|
|
if (pszModule == NULL) {
|
|
DPF(dlError, "[SeiInit] Failed to allocate %d bytes\n", dwModuleNameLen);
|
|
goto cleanup;
|
|
}
|
|
|
|
RtlCopyMemory(pszModule, lpszModuleToShim, dwModuleNameLen);
|
|
|
|
g_DynamicTokens[dwDynamicToken].pszModule = pszModule;
|
|
|
|
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 || g_bHookAllGetProcAddress) {
|
|
//
|
|
// 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 (pwszDLLPath) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pwszDLLPath);
|
|
}
|
|
|
|
if (pszCmdLine) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pszCmdLine);
|
|
}
|
|
|
|
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
|
|
|
|
if (g_DynamicTokens[dwDynamicToken].pszModule) {
|
|
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap,
|
|
0,
|
|
g_DynamicTokens[dwDynamicToken].pszModule);
|
|
|
|
g_DynamicTokens[dwDynamicToken].pszModule = NULL;
|
|
}
|
|
|
|
//
|
|
// Mark this slot as available.
|
|
//
|
|
g_DynamicTokens[dwDynamicToken].bToken = 0;
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bResult && lpdwDynamicToken) {
|
|
*lpdwDynamicToken = dwDynamicToken;
|
|
}
|
|
|
|
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;
|
|
|
|
SeiInitDebugSupport();
|
|
|
|
//
|
|
// 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, NULL);
|
|
}
|
|
|
|
#else
|
|
|
|
void
|
|
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 wszCommandLine[MAX_PATH];
|
|
DWORD dwExit;
|
|
PVOID hModule;
|
|
WCHAR wszTemp[MAX_PATH];
|
|
UNICODE_STRING ustrTemp;
|
|
UNICODE_STRING ustrGuid;
|
|
STARTUPINFOW StartupInfo;
|
|
PROCESS_INFORMATION ProcessInfo;
|
|
APPHELP_DATA ApphelpData;
|
|
BOOL bKillProcess = TRUE;
|
|
LPWSTR pszCommandLine;
|
|
DWORD dwLen;
|
|
|
|
|
|
ZeroMemory(&ApphelpData, sizeof(ApphelpData));
|
|
|
|
SdbReadApphelpData(hSDB, pSdbQuery->trAppHelp, &ApphelpData);
|
|
|
|
//
|
|
// 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");
|
|
goto terminate;
|
|
}
|
|
|
|
if (!SdbGetDatabaseGUID(hSDB, pdb, &guidDB)) {
|
|
DPF(dlError, "[SeiDisplayAppHelp] Failed to get DB guid.\n");
|
|
goto terminate;
|
|
}
|
|
|
|
if (RtlStringFromGUID(&guidDB, &ustrGuid) != STATUS_SUCCESS) {
|
|
DPF(dlError, "[SeiDisplayAppHelp] Failed to convert guid to string.\n");
|
|
goto terminate;
|
|
}
|
|
|
|
//
|
|
// We need a hack here to get kernel32.dll to initialize. We load aclayers.dll
|
|
// to trigger the init routine of kernel32.
|
|
//
|
|
SdbpGetAppPatchDir(hSDB, wszTemp, MAX_PATH);
|
|
StringCchCatW(wszTemp, MAX_PATH, L"\\aclayers.dll");
|
|
RtlInitUnicodeString(&ustrTemp, wszTemp);
|
|
|
|
//
|
|
// Construct a full path for ahui.exe
|
|
//
|
|
dwLen = GetSystemDirectoryW(wszCommandLine, ARRAYSIZE(wszCommandLine));
|
|
if (dwLen == 0 || dwLen > (ARRAYSIZE(wszCommandLine) - 2)) {
|
|
//
|
|
// Punt and leave out the system dir. Try to find it on the path.
|
|
//
|
|
pszCommandLine = wszCommandLine;
|
|
dwLen = 0;
|
|
} else {
|
|
|
|
pszCommandLine = wszCommandLine + dwLen;
|
|
*pszCommandLine++ = L'\\';
|
|
*pszCommandLine = 0;
|
|
dwLen++;
|
|
}
|
|
|
|
StringCchPrintfW(pszCommandLine,
|
|
ARRAYSIZE(wszCommandLine) - dwLen,
|
|
L"ahui.exe %s 0x%x",
|
|
ustrGuid.Buffer,
|
|
tiExe);
|
|
|
|
//
|
|
// Save the name of the DLL that we're about to load so we don't screw
|
|
// it's init routine.
|
|
//
|
|
StringCchCopyW(g_wszShimDllInLoading, MAX_MOD_LEN, 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,
|
|
CREATE_PRESERVE_CODE_AUTHZ_LEVEL, // bypass safer.
|
|
NULL, NULL,
|
|
&StartupInfo, &ProcessInfo)) {
|
|
DPF(dlError, "[SeiDisplayAppHelp] Failed to launch apphelp process.\n");
|
|
goto terminate;
|
|
}
|
|
|
|
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
|
|
|
|
GetExitCodeProcess(ProcessInfo.hProcess, &dwExit);
|
|
|
|
if (dwExit) {
|
|
bKillProcess = FALSE;
|
|
}
|
|
|
|
terminate:
|
|
|
|
if (ApphelpData.dwSeverity == APPHELP_HARDBLOCK || bKillProcess) {
|
|
SeiResetEntryProcessed(NtCurrentPeb());
|
|
TerminateProcess(GetCurrentProcess(), 0);
|
|
}
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
RtlInitializeCriticalSection(&g_csEng);
|
|
|
|
//
|
|
// Check if the image is a COM+ image
|
|
//
|
|
SeiCheckComPlusImage(Peb);
|
|
|
|
SeiInit(pstrFullPath->Buffer, hSDB, &sdbQuery, NULL, FALSE, NULL);
|
|
|
|
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,
|
|
OUT LPDWORD lpdwDynamicToken // this is what you use to call SE_DynamicUnshim.
|
|
)
|
|
/*++
|
|
Return: TRUE on success, FALSE otherwise.
|
|
|
|
Desc: This function attempts to inject shims dynamically.
|
|
--*/
|
|
{
|
|
BOOL bReturn = FALSE;
|
|
|
|
SeiInitDebugSupport();
|
|
|
|
if (lpszModuleToShim != NULL && *lpszModuleToShim == 0) {
|
|
lpszModuleToShim = NULL;
|
|
}
|
|
|
|
if (lpdwDynamicToken == NULL && lpszModuleToShim == NULL) {
|
|
DPF(dlError,
|
|
"[SE_DynamicShim] if you don't specify a module to shim, you must specify a "
|
|
"valid lpdwDynamicToken\n");
|
|
return bReturn;
|
|
}
|
|
|
|
RtlEnterCriticalSection(&g_csEng);
|
|
|
|
bReturn = SeiInit(lpszFullPath, hSDB, psdbQuery, lpszModuleToShim, TRUE, lpdwDynamicToken);
|
|
|
|
#ifndef SE_WIN2K
|
|
LdrInitShimEngineDynamic(g_hModule);
|
|
#endif
|
|
|
|
RtlLeaveCriticalSection(&g_csEng);
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
BOOL
|
|
SE_DynamicUnshim(
|
|
IN DWORD dwDynamicToken
|
|
)
|
|
/*++
|
|
Return: TRUE if successfully unshimmed; FALSE otherwise.
|
|
|
|
Desc: This function does 4 things:
|
|
1) unhook the imports from all hooked modules;
|
|
|
|
2) remove the shims with this magic number from the global shim info
|
|
array;
|
|
|
|
3) remove the shims with this magic number from the global hook
|
|
array and adjust the HOOKAPIEX structure for the shims left:
|
|
+ decrease dwShimID if needed;
|
|
+ setting pTopOfChain to NULL if needed, so when we call
|
|
SeiConstructChain the new chain will get constructed.
|
|
|
|
4) repatch the import tables with the shims left.
|
|
--*/
|
|
{
|
|
DWORD dwSrcIndex, dwDestIndex, dwShimsToRemove, dwHookIndex, i;
|
|
PHOOKAPIEX pHookEx;
|
|
|
|
ASSERT(dwDynamicToken < MAX_DYNAMIC_SHIMS);
|
|
ASSERT(g_DynamicTokens[dwDynamicToken].bToken != 0);
|
|
|
|
g_DynamicTokens[dwDynamicToken].bToken = 0;
|
|
|
|
if (g_DynamicTokens[dwDynamicToken].pszModule) {
|
|
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap,
|
|
0,
|
|
g_DynamicTokens[dwDynamicToken].pszModule);
|
|
|
|
g_DynamicTokens[dwDynamicToken].pszModule = NULL;
|
|
}
|
|
|
|
if (g_dwShimsCount == 0) {
|
|
DPF(dlError, "[SE_DynamicUnshim] This process is not shimmed!!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
RtlEnterCriticalSection(&g_csEng);
|
|
|
|
//
|
|
// First let's unhook everything.
|
|
//
|
|
for (i = 0; i < g_dwHookedModuleCount; i++) {
|
|
SeiUnhookImports(g_hHookedModules[i].pDllBase,
|
|
g_hHookedModules[i].szModuleName,
|
|
TRUE);
|
|
}
|
|
|
|
//
|
|
// The shims with the same magic number are contiguous in the array so
|
|
// we just need to find out where this contiguous block starts and ends.
|
|
//
|
|
dwSrcIndex = 0;
|
|
|
|
while (dwSrcIndex < g_dwShimsCount - 1 &&
|
|
g_pShimInfo[dwSrcIndex].dwDynamicToken != dwDynamicToken) {
|
|
|
|
dwSrcIndex++;
|
|
}
|
|
|
|
dwDestIndex = dwSrcIndex + 1;
|
|
|
|
while (dwDestIndex < g_dwShimsCount &&
|
|
g_pShimInfo[dwDestIndex].dwDynamicToken == dwDynamicToken) {
|
|
|
|
dwDestIndex++;
|
|
}
|
|
|
|
if (dwDestIndex < g_dwShimsCount) {
|
|
//
|
|
// First free the memory we allocated on our heap.
|
|
//
|
|
for (i = dwSrcIndex; i < dwDestIndex; ++i) {
|
|
|
|
SeiEmptyInclExclList(i);
|
|
|
|
for (dwHookIndex = 0; dwHookIndex < g_pShimInfo[i].dwHookedAPIs; ++dwHookIndex) {
|
|
|
|
pHookEx = g_pHookArray[i][dwHookIndex].pHookEx;
|
|
|
|
if (pHookEx) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pHookEx);
|
|
g_pHookArray[i][dwHookIndex].pHookEx = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Then overwrite the entries that need to be removed.
|
|
//
|
|
RtlCopyMemory(&g_pShimInfo[dwSrcIndex],
|
|
&g_pShimInfo[dwDestIndex],
|
|
sizeof(SHIMINFO) * (g_dwShimsCount - dwDestIndex));
|
|
|
|
RtlCopyMemory(&g_pHookArray[dwSrcIndex],
|
|
&g_pHookArray[dwDestIndex],
|
|
sizeof(PHOOKAPI) * (g_dwShimsCount - dwDestIndex));
|
|
}
|
|
|
|
//
|
|
// Zero out the extra entries.
|
|
//
|
|
dwShimsToRemove = dwDestIndex - dwSrcIndex;
|
|
|
|
ZeroMemory(&g_pShimInfo[dwSrcIndex + (g_dwShimsCount - dwDestIndex)],
|
|
sizeof(SHIMINFO) * dwShimsToRemove);
|
|
ZeroMemory(&g_pHookArray[dwSrcIndex + (g_dwShimsCount - dwDestIndex)],
|
|
sizeof(HOOKAPI) * dwShimsToRemove);
|
|
|
|
//
|
|
// Adjust the HOOKAPIEX structures.
|
|
//
|
|
for (i = 0; g_pShimInfo[i].pDllBase; ++i) {
|
|
for (dwHookIndex = 0; dwHookIndex < g_pShimInfo[i].dwHookedAPIs; ++dwHookIndex) {
|
|
|
|
pHookEx = g_pHookArray[i][dwHookIndex].pHookEx;
|
|
|
|
//
|
|
// This will cause the chain to be reconstructed.
|
|
//
|
|
pHookEx->pTopOfChain = NULL;
|
|
|
|
if (i >= dwSrcIndex) {
|
|
//
|
|
// Need to adjust the shim ID.
|
|
//
|
|
pHookEx->dwShimID -= dwShimsToRemove;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Adjust the global variables.
|
|
//
|
|
g_dwShimsCount -= dwShimsToRemove;
|
|
|
|
//
|
|
// If g_dwShimsCount is 1 it means there's only the shimeng internal hook left, in which
|
|
// case we don't need to repatch anything.
|
|
//
|
|
if (g_dwShimsCount > 1) {
|
|
//
|
|
// Repatch the import tables using the shims left.
|
|
//
|
|
PatchNewModules(TRUE);
|
|
}
|
|
|
|
RtlLeaveCriticalSection(&g_csEng);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#ifndef SE_WIN2K
|
|
|
|
PLIST_ENTRY
|
|
SeiFindTaskEntry(
|
|
IN PLIST_ENTRY pHead,
|
|
IN ULONG uTaskToFind
|
|
)
|
|
{
|
|
PNTVDMTASK pNTVDMTask;
|
|
PLIST_ENTRY pEntry;
|
|
|
|
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
|
|
pNTVDMTask = CONTAINING_RECORD(pEntry, NTVDMTASK, entry);
|
|
|
|
if (pNTVDMTask->uTask == uTaskToFind) {
|
|
return pEntry;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
SeiDeleteTaskEntry(
|
|
IN PLIST_ENTRY pEntryToDelete
|
|
)
|
|
{
|
|
PNTVDMTASK pNTVDMTask;
|
|
|
|
pNTVDMTask = CONTAINING_RECORD(pEntryToDelete, NTVDMTASK, entry);
|
|
RemoveEntryList(pEntryToDelete);
|
|
|
|
if (pNTVDMTask->pHookArray) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pNTVDMTask->pHookArray);
|
|
}
|
|
|
|
if (pNTVDMTask->pShimInfo) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pNTVDMTask->pShimInfo);
|
|
}
|
|
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pNTVDMTask);
|
|
}
|
|
|
|
BOOL
|
|
SeiInitNTVDM(
|
|
IN LPCWSTR pwszApp,
|
|
IN HSDB hSDB,
|
|
IN SDBQUERYRESULT* psdbQuery,
|
|
IN OUT PVDMTABLE pVDMTable
|
|
)
|
|
/*++
|
|
Return: TRUE on success, FALSE otherwise.
|
|
|
|
Desc: Injects all the shims and patches specified for this 16 bit task
|
|
in the database.
|
|
--*/
|
|
{
|
|
PPEB Peb = NtCurrentPeb();
|
|
BOOL bResult = FALSE;
|
|
TAGREF trShimRef;
|
|
NTSTATUS status;
|
|
DWORD dwCounter = 0;
|
|
DWORD dwTotalHooks = 0;
|
|
DWORD dwShimsCount = 0;
|
|
PHOOKAPI* pHookArray = NULL;
|
|
PSHIMINFO pShimInfo;
|
|
int nShimRef;
|
|
PTRSHIMARRAY pShimArray = NULL;
|
|
ULONG uTask;
|
|
PLIST_ENTRY pTaskEntry = NULL;
|
|
PNTVDMTASK pNTVDMTask = NULL;
|
|
|
|
static WCHAR wszDLLPath[MAX_PATH];
|
|
static WCHAR wszShimName[MAX_PATH];
|
|
static CHAR szCmdLine[SHIM_COMMAND_LINE_MAX_BUFFER];
|
|
|
|
uTask = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread);
|
|
|
|
if (!SeiInitGlobals(pwszApp)) {
|
|
DPF(dlError, "[SeiInitNTVDM] Failed to initialize global data\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check if we already have an entry for this task, if not allocate a new
|
|
// entry.
|
|
//
|
|
pTaskEntry = SeiFindTaskEntry(&g_listNTVDMTasks, uTask);
|
|
|
|
if (pTaskEntry == NULL) {
|
|
|
|
//
|
|
// NTVDM calls this function with the same hSDB and psdbQuery in the same
|
|
// task so we only need to look at them the first time.
|
|
//
|
|
pShimArray = SeiBuildShimRefArray(hSDB,
|
|
psdbQuery,
|
|
&dwShimsCount,
|
|
TRUE,
|
|
TRUE,
|
|
FALSE);
|
|
|
|
if (pShimArray == NULL) {
|
|
DPF(dlError, "[SeiInitNTVDM] Failed to build the shimref array\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If there are no shims, just return.
|
|
//
|
|
if (dwShimsCount == 0) {
|
|
DPF(dlPrint, "[SeiInitNTVDM] No SHIMs for this app \"%S\".\n", pwszApp);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Debug spew for matching notification.
|
|
//
|
|
DPF(dlPrint, "[SeiInitNTVDM] Matched entry: \"%S\"\n", pwszApp);
|
|
|
|
if (g_pwszShimViewerData) {
|
|
//
|
|
// Send the name of the process to the pipe
|
|
//
|
|
StringCchPrintfW(g_pwszShimViewerData, SHIMVIEWER_DATA_SIZE, L"New task created: %s", pwszApp);
|
|
SeiDbgPrint();
|
|
}
|
|
|
|
//
|
|
// Allocate a new entry for this task.
|
|
//
|
|
pNTVDMTask = (PNTVDMTASK)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(NTVDMTASK));
|
|
|
|
if (pNTVDMTask == NULL) {
|
|
DPF(dlError,
|
|
"[SeiInitNTVDM] Failure allocating %d bytes for the new Task entry\n",
|
|
sizeof(NTVDMTASK));
|
|
goto cleanup;
|
|
}
|
|
|
|
InsertHeadList(&g_listNTVDMTasks, &pNTVDMTask->entry);
|
|
|
|
pNTVDMTask->uTask = uTask;
|
|
|
|
//
|
|
// Allocate a storage pointer for the hook information.
|
|
//
|
|
pNTVDMTask->pHookArray = (PHOOKAPI*)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(PHOOKAPI) * dwShimsCount);
|
|
if (pNTVDMTask->pHookArray == NULL) {
|
|
DPF(dlError,
|
|
"[SeiInitNTVDM] Failure allocating %d bytes for the hook array\n",
|
|
sizeof(PHOOKAPI) * dwShimsCount);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Allocate the array that keeps information about the shims.
|
|
//
|
|
pNTVDMTask->pShimInfo = (PSHIMINFO)(*g_pfnRtlAllocateHeap)(g_pShimHeap,
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(SHIMINFO) * dwShimsCount);
|
|
if (pNTVDMTask->pShimInfo == NULL) {
|
|
DPF(dlError,
|
|
"[SeiInitNTVDM] Failure allocating %d bytes for the SHIMINFO array\n",
|
|
sizeof(SHIMINFO) * dwShimsCount);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Point the local variables to the beginning of the arrays.
|
|
//
|
|
pHookArray = pNTVDMTask->pHookArray;
|
|
pShimInfo = pNTVDMTask->pShimInfo;
|
|
|
|
//
|
|
// 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;
|
|
|
|
trShimRef = pShimArray->parrShimRef[nShimRef].trShimRef;
|
|
|
|
//
|
|
// Retrieve the shim name.
|
|
//
|
|
wszShimName[0] = 0;
|
|
trShimName = SdbFindFirstTagRef(hSDB, trShimRef, TAG_NAME);
|
|
if (trShimName == TAGREF_NULL) {
|
|
DPF(dlError, "[SeiInitNTVDM] Could not retrieve shim name tag from entry.\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!SdbReadStringTagRef(hSDB, trShimName, wszShimName, MAX_PATH)) {
|
|
DPF(dlError, "[SeiInitNTVDM] Could not retrieve shim name from entry.\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check for duplicate shims.
|
|
//
|
|
for (i = 0; i < dwShimsCount; ++i) {
|
|
|
|
if (_wcsnicmp(pShimInfo[i].wszName, wszShimName, MAX_SHIM_NAME_LEN - 1) == 0) {
|
|
goto nextShim;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Save off the name of the shim.
|
|
//
|
|
StringCchCopyW(pShimInfo[dwCounter].wszName, MAX_SHIM_NAME_LEN, wszShimName);
|
|
|
|
if (!SdbGetDllPath(hSDB, trShimRef, wszDLLPath, MAX_PATH)) {
|
|
DPF(dlError, "[SeiInitNTVDM] 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)) {
|
|
|
|
status = LdrLoadDll(UNICODE_NULL, NULL, &UnicodeString, &pModuleHandle);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
DPF(dlError,
|
|
"[SeiInitNTVDM] Failed to load DLL \"%S\" Status 0x%lx\n",
|
|
wszDLLPath, status);
|
|
goto cleanup;
|
|
}
|
|
|
|
DPF(dlPrint,
|
|
"[SeiInitNTVDM] Shim DLL 0x%X \"%S\" loaded\n",
|
|
pModuleHandle,
|
|
wszDLLPath);
|
|
}
|
|
|
|
DPF(dlPrint, "[SeiInitNTVDM] Using SHIM \"%S!%S\"\n",
|
|
wszShimName, pwszDllShortName);
|
|
|
|
pShimInfo[dwCounter].pDllBase = pModuleHandle;
|
|
|
|
//
|
|
// Check for command line.
|
|
//
|
|
if (SeiGetShimCommandLine(hSDB, trShimRef, szCmdLine)) {
|
|
DPF(dlPrint,
|
|
"[SeiInitNTVDM] Command line for Shim \"%S\" : \"%s\"\n",
|
|
wszShimName,
|
|
szCmdLine);
|
|
}
|
|
|
|
if (g_pwszShimViewerData) {
|
|
//
|
|
// Send this shim name to the pipe
|
|
//
|
|
StringCchPrintfW(g_pwszShimViewerData,
|
|
SHIMVIEWER_DATA_SIZE,
|
|
L"%s - Applying shim %s(%S) from %s",
|
|
g_szExeName,
|
|
wszShimName,
|
|
szCmdLine,
|
|
pwszDllShortName);
|
|
|
|
SeiDbgPrint();
|
|
}
|
|
|
|
//
|
|
// Get the GetHookApis entry point.
|
|
//
|
|
RtlInitString(&ProcedureNameString, "GetHookAPIs");
|
|
|
|
status = LdrGetProcedureAddress(pModuleHandle,
|
|
&ProcedureNameString,
|
|
0,
|
|
(PVOID*)&pfnGetHookApis);
|
|
|
|
if (!NT_SUCCESS(status) || pfnGetHookApis == NULL) {
|
|
DPF(dlError,
|
|
"[SeiInitNTVDM] Failed to get 'GetHookAPIsEx' 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);
|
|
|
|
DPF(dlInfo,
|
|
"[SeiInitNTVDM] GetHookAPIsEx 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) {
|
|
pHookArray[dwCounter][i].pHookEx = NULL;
|
|
}
|
|
}
|
|
|
|
dwCounter++;
|
|
|
|
nextShim:
|
|
|
|
//
|
|
// Go to the next shim ref.
|
|
//
|
|
nShimRef++;
|
|
}
|
|
|
|
pNTVDMTask->dwShimsCount = dwCounter;
|
|
|
|
//
|
|
// For 16-bit tasks, all 32-bit dlls are loaded up front so we only need to
|
|
// resolve the API addresses once.
|
|
//
|
|
SeiResolveAPIs(pNTVDMTask);
|
|
|
|
} else {
|
|
|
|
pNTVDMTask = CONTAINING_RECORD(pTaskEntry, NTVDMTASK, entry);
|
|
}
|
|
|
|
//
|
|
// Walk the hook list and patch the shimmed APIs.
|
|
//
|
|
SeiHookNTVDM(pwszApp, pVDMTable, pNTVDMTask);
|
|
|
|
bResult = TRUE;
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// Cleanup
|
|
//
|
|
if (pShimArray) {
|
|
if (pShimArray->parrShimRef) {
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pShimArray->parrShimRef);
|
|
}
|
|
(*g_pfnRtlFreeHeap)(g_pShimHeap, 0, pShimArray);
|
|
}
|
|
|
|
if (!bResult) {
|
|
if (pNTVDMTask) {
|
|
SeiDeleteTaskEntry(&pNTVDMTask->entry);
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
if (!bResult) {
|
|
DbgPrint("[SeiInitNTVDM] Shim engine failed to initialize.\n");
|
|
}
|
|
#endif // DBG
|
|
|
|
return bResult;
|
|
}
|
|
|
|
// If you change the parameters to this, please update the prototype in shimdb.w
|
|
BOOL
|
|
SE_ShimNTVDM(
|
|
IN LPCWSTR pwszApp,
|
|
IN HSDB hSDB,
|
|
IN SDBQUERYRESULT* psdbQuery,
|
|
IN PVDMTABLE pVDMTable
|
|
)
|
|
/*++
|
|
Return: TRUE on success, FALSE otherwise.
|
|
|
|
Desc: This function attempts to inject shims dynamically.
|
|
--*/
|
|
{
|
|
g_bNTVDM = TRUE;
|
|
|
|
SeiInitDebugSupport();
|
|
|
|
SeiInitNTVDM(pwszApp, hSDB, psdbQuery, pVDMTable);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// If you change the parameters to this, please update the prototype in shimdb.w
|
|
void
|
|
SE_RemoveNTVDMTask(
|
|
IN ULONG uTask
|
|
)
|
|
{
|
|
PLIST_ENTRY pTaskEntry;
|
|
|
|
pTaskEntry = SeiFindTaskEntry(&g_listNTVDMTasks, uTask);
|
|
|
|
if (pTaskEntry == NULL) {
|
|
DPF(dlWarning, "[SE_RemoveNTVDMTask] task 0x%lx is not shimmed\n", uTask);
|
|
} else {
|
|
SeiDeleteTaskEntry(pTaskEntry);
|
|
DPF(dlInfo, "[SE_RemoveNTVDMTask] task 0x%lx was removed\n", uTask);
|
|
}
|
|
}
|
|
|
|
#endif // SE_WIN2K
|
|
|
|
BOOL
|
|
SeiUnhookImports(
|
|
IN PBYTE pDllBase, // the base address of the DLL to be hooked
|
|
IN LPCSTR pszDllName, // the name of the DLL to be hooked
|
|
IN BOOL bRevertPfnOld // specify TRUE if you want the pfnOld in the
|
|
// hook array to be reverted back to the original
|
|
// function pointer.
|
|
|
|
)
|
|
/*++
|
|
Return: STATUS_SUCCESS if successful.
|
|
|
|
Desc: Walks the import table of the specified module and unhook the APIs that
|
|
were hooked.
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
BOOL bAnyHooked = FALSE;
|
|
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pDllBase;
|
|
PIMAGE_NT_HEADERS pINTH;
|
|
PIMAGE_IMPORT_DESCRIPTOR pIID;
|
|
DWORD dwImportTableOffset;
|
|
PHOOKAPI pHook;
|
|
DWORD dwOldProtect, dwOldProtect2;
|
|
SIZE_T dwProtectSize;
|
|
DWORD i, j;
|
|
PVOID pfnNew, pfnOld;
|
|
|
|
//
|
|
// 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, "[SeiUnhookImports] Unhooking module 0x%p \"%s\"\n", pDllBase, pszDllName);
|
|
|
|
pIID = (PIMAGE_IMPORT_DESCRIPTOR)(pDllBase + dwImportTableOffset);
|
|
|
|
//
|
|
// Loop through the import table and search for the APIs that we want to unhook.
|
|
//
|
|
for (;;) {
|
|
|
|
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 hooked APIs in this module!
|
|
//
|
|
pITDA = (PIMAGE_THUNK_DATA)(pDllBase + (DWORD)pIID->FirstThunk);
|
|
|
|
for (;;) {
|
|
|
|
SIZE_T dwFuncAddr;
|
|
|
|
pfnNew = (PVOID)pITDA->u1.Function;
|
|
|
|
//
|
|
// Done with all the imports from this module? (terminating condition)
|
|
//
|
|
if (pITDA->u1.Ordinal == 0) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Loop through the HOOKAPI list and find the HOOKAPI that has this function pointer.
|
|
//
|
|
for (i = g_dwShimsCount - 1; (LONG)i >= 0; i--) {
|
|
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
|
|
|
|
if (g_pHookArray[i][j].pfnNew == pfnNew) {
|
|
|
|
//
|
|
// Go to the end of the chain and find the original import.
|
|
//
|
|
pHook = &g_pHookArray[i][j];
|
|
while (pHook && pHook->pHookEx && pHook->pHookEx->pNext) {
|
|
pHook = pHook->pHookEx->pNext;
|
|
}
|
|
|
|
pfnOld = pHook->pfnOld;
|
|
|
|
if (bRevertPfnOld) {
|
|
g_pHookArray[i][j].pfnOld = pfnOld;
|
|
}
|
|
|
|
//
|
|
// Make the code page writable and overwrite new function pointer
|
|
// in the import table with the original function pointer.
|
|
//
|
|
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)pfnOld;
|
|
|
|
dwProtectSize = sizeof(DWORD);
|
|
|
|
status = NtProtectVirtualMemory(NtCurrentProcess(),
|
|
(PVOID)&dwFuncAddr,
|
|
&dwProtectSize,
|
|
dwOldProtect,
|
|
&dwOldProtect2);
|
|
if (!NT_SUCCESS(status)) {
|
|
DPF(dlError, "[SeiUnhookImports] Failed to change back the protection\n");
|
|
}
|
|
} else {
|
|
DPF(dlError,
|
|
"[SeiUnhookImports] Failed 0x%X to change protection to PAGE_READWRITE."
|
|
" Addr 0x%p\n",
|
|
status,
|
|
&pITDA->u1.Function);
|
|
}
|
|
|
|
goto UnhookDone;
|
|
}
|
|
}
|
|
}
|
|
UnhookDone:
|
|
pITDA++;
|
|
}
|
|
pIID++;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
SeiUnhook(
|
|
void
|
|
)
|
|
/*++
|
|
Return: TRUE on success, FALSE otherwise.
|
|
|
|
Desc: This function unhooks all the shims.
|
|
--*/
|
|
{
|
|
DWORD i, j;
|
|
|
|
if (g_dwShimsCount == 0) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < g_dwHookedModuleCount; i++) {
|
|
SeiUnhookImports(g_hHookedModules[i].pDllBase, g_hHookedModules[i].szModuleName, FALSE);
|
|
g_hHookedModules[i].pDllBase = NULL;
|
|
StringCchCopyA(g_hHookedModules[i].szModuleName, MAX_MOD_LEN, "removed!");
|
|
}
|
|
|
|
for (i = 0; i < g_dwShimsCount; i++) {
|
|
for (j = 0; j < g_pShimInfo[i].dwHookedAPIs; j++) {
|
|
if (g_pHookArray[i][j].pszModule != NULL) {
|
|
g_pHookArray[i][j].pfnOld = NULL;
|
|
g_pShimInfo[i].dwFlags &= ~SIF_RESOLVED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_pShimHeap != NULL) {
|
|
RtlDestroyHeap(g_pShimHeap);
|
|
g_pShimHeap = NULL;
|
|
}
|
|
|
|
//
|
|
// Reset the globals.
|
|
//
|
|
g_dwHookedModuleCount = 0;
|
|
g_pGlobalInclusionList = NULL;
|
|
g_pHookArray = NULL;
|
|
g_hModule = NULL;
|
|
g_dwShimsCount = 0;
|
|
g_dwMaxShimsCount = 0;
|
|
g_bShimInitialized = FALSE;
|
|
g_bShimDuringInit = FALSE;
|
|
g_bInitGlobals = FALSE;
|
|
g_bHookAllGetProcAddress = FALSE;
|
|
}
|
|
|
|
BOOL
|
|
SeiDbgPrint(
|
|
void
|
|
)
|
|
{
|
|
if (g_eShimViewerOption == SHIMVIEWER_OPTION_YES) {
|
|
OutputDebugStringW(g_wszFullShimViewerData);
|
|
}
|
|
|
|
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) {
|
|
|
|
if (!g_bInitGlobals) {
|
|
RtlInitializeCriticalSection(&g_csEng);
|
|
InitializeListHead(&g_listNTVDMTasks);
|
|
g_hModule = (HMODULE)hInstance;
|
|
}
|
|
|
|
} else if (dwreason == DLL_PROCESS_DETACH) {
|
|
SeiUnhook();
|
|
}
|
|
return TRUE;
|
|
|
|
UNREFERENCED_PARAMETER(reserved);
|
|
}
|