|
|
/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
EmulateMissingEXE.cpp
Abstract:
Win9x had scandskw.exe and defrag.exe in %windir%, NT does not. Whistler has a hack in the shell32 for scandisk for app compatability purposes. Whistler can also invoke defrag via "%windir%\system32\mmc.exe %windir%\system32\dfrg.msc".
This shim redirects CreateProcess and Winexec to execute these two substitutes, as well as FindFile to indicate their presence.
Notes:
This is a general purpose shim.
History:
01/02/2001 prashkud Created 02/18/2001 prashkud Merged HandleStartKeyword SHIM with this. 02/21/2001 prashkud Replaced most strings with CString class.
--*/
#include "precomp.h"
IMPLEMENT_SHIM_BEGIN(EmulateMissingEXE) #include "ShimHookMacro.h"
APIHOOK_ENUM_BEGIN APIHOOK_ENUM_ENTRY(CreateProcessA) APIHOOK_ENUM_ENTRY(CreateProcessW) APIHOOK_ENUM_ENTRY(WinExec) APIHOOK_ENUM_ENTRY(FindFirstFileA) APIHOOK_ENUM_ENTRY(FindFirstFileW) APIHOOK_ENUM_ENTRY_COMSERVER(SHELL32) APIHOOK_ENUM_END
IMPLEMENT_COMSERVER_HOOK(SHELL32)
// Type for the functions that builds the New EXES
typedef BOOL (*_pfn_STUBFUNC)(CString&, CString&, BOOL);
// Main Data structure to hold the New strings
struct REPLACEENTRY { WCHAR *OrigExeName; // original EXE to be replaced
_pfn_STUBFUNC pfnFuncName; // function to call to correct the name
};
CRITICAL_SECTION g_CritSec; WCHAR g_szSysDir[MAX_PATH]; // system directory for stubs to use
BOOL StubScandisk(CString&, CString&, BOOL); BOOL StubDefrag(CString&, CString&, BOOL); BOOL StubStart(CString&, CString&, BOOL); BOOL StubControl(CString&, CString&, BOOL); BOOL StubDxDiag(CString&, CString&, BOOL); BOOL StubWinhlp(CString&, CString&, BOOL); BOOL StubRundll(CString&, CString&, BOOL); BOOL StubPbrush(CString&, CString&, BOOL);
// Add variations of these missing Exes like in HandleStartKeyword
// Start has been put at the top of the list as there seem to be more apps
// that need the SHIM for this EXE than others. In fact there was a
// seperate SHIM HandleStartKeyword that was merged with this.
REPLACEENTRY g_ReplList[] = { {L"start", StubStart }, {L"start.exe", StubStart }, {L"scandskw", StubScandisk }, {L"scandskw.exe", StubScandisk }, {L"defrag", StubDefrag }, {L"defrag.exe", StubDefrag }, {L"control", StubControl }, {L"control.exe", StubControl }, {L"dxdiag", StubDxDiag }, {L"dxdiag.exe", StubDxDiag }, {L"winhelp", StubWinhlp }, {L"winhelp.exe", StubWinhlp }, {L"rundll", StubRundll }, {L"rundll.exe", StubRundll }, {L"Pbrush", StubPbrush }, {L"Pbrush.exe", StubPbrush }, // Always the last one
{L"", NULL } };
// Added to merge HandleStartKeyword
// Link list of shell link object this pointers.
struct THISPOINTER { THISPOINTER *next; LPCVOID pThisPointer; };
THISPOINTER *g_pThisPointerList;
/*++
Function Description:
Add a this pointer to the linked list of pointers. Does not add if the pointer is NULL or a duplicate.
Arguments:
IN pThisPointer - the pointer to add.
Return Value:
None
History:
12/14/2000 maonis Created
--*/
VOID AddThisPointer( IN LPCVOID pThisPointer ) { EnterCriticalSection(&g_CritSec);
if (pThisPointer) { THISPOINTER *pPointer = g_pThisPointerList; while (pPointer) { if (pPointer->pThisPointer == pThisPointer) { return; } pPointer = pPointer->next; }
pPointer = (THISPOINTER *) malloc(sizeof THISPOINTER);
if (pPointer) { pPointer->pThisPointer = pThisPointer; pPointer->next = g_pThisPointerList; g_pThisPointerList = pPointer; } }
LeaveCriticalSection(&g_CritSec); }
/*++
Function Description:
Remove a this pointer if it can be found in the linked list of pointers.
Arguments:
IN pThisPointer - the pointer to remove.
Return Value:
TRUE if the pointer is found. FALSE if the pointer is not found.
History:
12/14/2000 maonis Created
--*/
BOOL RemoveThisPointer( IN LPCVOID pThisPointer ) { THISPOINTER *pPointer = g_pThisPointerList; THISPOINTER *last = NULL; BOOL lRet = FALSE; EnterCriticalSection(&g_CritSec);
while (pPointer) { if (pPointer->pThisPointer == pThisPointer) { if (last) { last->next = pPointer->next; } else { g_pThisPointerList = pPointer->next; }
free(pPointer); lRet = TRUE; break; }
last = pPointer; pPointer = pPointer->next; }
LeaveCriticalSection(&g_CritSec); return lRet; }
/*++
We are here because the application name: scandskw.exe, matches the one in the static array. Fill the News for scandskw.exe as:
rundll32.exe shell32.dll,AppCompat_RunDLL SCANDSKW
--*/
BOOL StubScandisk( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\rundll32.exe"; csNewCommandLine = L"shell32.dll,AppCompat_RunDLL SCANDSKW";
return TRUE;
}
/*++
We are here because the application name: defrag.exe, matches the one in the static array. Fill the News for .exe as: %windir%\\system32\\mmc.exe %windir%\\system32\\dfrg.msc
--*/
BOOL StubDefrag( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\mmc.exe";
csNewCommandLine = g_szSysDir; csNewCommandLine += L"\\dfrg.msc"; return TRUE; }
/*++
We are here because the application name: start.exe, matches the one in the static array. Fill the News for .exe as: %windir%\\system32\\cmd.exe" "/c start"
Many applications have a "start.exe" in their current working directories which needs to take precendence over any New we make.
--*/
BOOL StubStart( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { //
// First check the current working directory for start.exe
//
if (bExists) { return FALSE; }
//
// There is no start.exe in the current working directory
//
csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\cmd.exe"; csNewCommandLine = L"/d /c start \"\"";
return TRUE; }
/*++
We are here because the application name: control.exe, matches the one in the static array. Fill the News for .exe as: %windir%\\system32\\control.exe
--*/
BOOL StubControl( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\control.exe"; csNewCommandLine = L"";
return TRUE;
}
/*++
We are here because the application name: dxdiag.exe, matches the one in the static array. Fill the News for .exe as: %windir%\system32\dxdiag.exe
--*/
BOOL StubDxDiag( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\dxdiag.exe"; csNewCommandLine = L"";
return TRUE; }
/*++
We are here because the application name: Winhlp.exe, matches the one in the static array. Fill the News for .exe as: %windir%\system32\winhlp32.exe
--*/
BOOL StubWinhlp( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\winhlp32.exe"; // Winhlp32.exe needs the app name to be in the commandline.
csNewCommandLine = csNewApplicationName;
return TRUE; }
/*++
We are here because the application name: rundll.exe matches the one in the static array. Fill the News for .exe as: %windir%\system32\rundll32.exe
--*/
BOOL StubRundll( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\rundll32.exe"; csNewCommandLine = L"";
return TRUE; }
/*++
We are here because the application name: Pbrush.exe matches the one in the static array. Fill the New for .exe as: %windir%\system32\mspaint.exe
--*/
BOOL StubPbrush( CString& csNewApplicationName, CString& csNewCommandLine, BOOL bExists ) { csNewApplicationName = g_szSysDir; csNewApplicationName += L"\\mspaint.exe"; csNewCommandLine = L"";
return TRUE; }
/*++
GetTitle takes the app path and returns just the EXE name.
--*/
VOID GetTitle(CString& csAppName,CString& csAppTitle) { csAppTitle = csAppName; int len = csAppName.ReverseFind(L'\\'); if (len) { csAppTitle.Delete(0, len+1); } }
/*++
This is the main function where the New logic happens. This function goes through the static array and fills the suitable New appname and the commandline.
--*/
BOOL Redirect( const CString& csApplicationName, const CString& csCommandLine, CString& csNewApplicationName, CString& csNewCommandLine, BOOL bJustCheckExePresence ) { BOOL bRet = FALSE; CSTRING_TRY {
CString csOrigAppName; CString csOrigCommandLine; BOOL bExists = FALSE;
AppAndCommandLine AppObj(csApplicationName, csCommandLine); csOrigAppName = AppObj.GetApplicationName(); csOrigCommandLine = AppObj.GetCommandlineNoAppName();
if (csOrigAppName.IsEmpty()) { goto Exit; }
//
// Loop through the list of redirectors
//
REPLACEENTRY *rEntry = &g_ReplList[0]; CString csAppTitle; GetTitle(csOrigAppName, csAppTitle);
while (rEntry && rEntry->OrigExeName[0]) { if (_wcsicmp(rEntry->OrigExeName, csAppTitle) == 0) { //
// This final parameter has been added for the merger
// of HandleStartKeyword Shim. If this is TRUE, we don't
// go any further but just return.
//
if (bJustCheckExePresence) { bRet = TRUE; goto Exit; }
//
// Check if the current working directory contains the exe in question
//
WCHAR szCurrentDirectory[MAX_PATH]; if (szCurrentDirectory && GetCurrentDirectoryW(MAX_PATH, szCurrentDirectory)) { CString csFullAppName(szCurrentDirectory); csFullAppName += L"\\"; csFullAppName += csAppTitle; // Check if the file exists and is not a directory
DWORD dwAttr = GetFileAttributesW(csFullAppName); if ((dwAttr != 0xFFFFFFFF) && !(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) { DPFN( eDbgLevelInfo, "[Redirect] %s found in current working directory"); bExists = TRUE; } } //
// We have a match, so call the corresponding function
//
if (bRet = (*(rEntry->pfnFuncName))(csNewApplicationName, csNewCommandLine, bExists)) { //
// Append the original command line
//
csNewCommandLine += L" "; csNewCommandLine += csOrigCommandLine; }
// We matched an EXE, so we're done
break; }
rEntry++; }
if (bRet) { DPFN( eDbgLevelWarning, "Redirected:"); DPFN( eDbgLevelWarning, "\tFrom: %S %S", csApplicationName, csCommandLine); DPFN( eDbgLevelWarning, "\tTo: %S %S", csNewApplicationName, csNewCommandLine); } } CSTRING_CATCH { DPFN( eDbgLevelError, "Not Redirecting: Exception encountered"); bRet = FALSE; }
Exit: return bRet; }
/*++
Hooks the CreateProcessA function to see if any News need to be substituted.
--*/
BOOL APIHOOK(CreateProcessA)( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { if ((NULL == lpApplicationName) && (NULL == lpCommandLine)) { // If both are NULL, return FALSE.
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
CSTRING_TRY { CString csNewApplicationName; CString csNewCommandLine; CString csPassedAppName(lpApplicationName); CString csPassedCommandLine(lpCommandLine); if ((csPassedAppName.IsEmpty()) && (csPassedCommandLine.IsEmpty())) { goto exit; }
//
// Run the list of New stubs: call to the main New routine
//
if (Redirect(csPassedAppName, csPassedCommandLine, csNewApplicationName, csNewCommandLine, FALSE)) { LOGN( eDbgLevelWarning, "[CreateProcessA] \" %s %s \": changed to \" %s %s \"", lpApplicationName, lpCommandLine, csNewApplicationName.GetAnsi(), csNewCommandLine.GetAnsi()); } else { csNewApplicationName = lpApplicationName; csNewCommandLine = lpCommandLine; }
// Convert back to ANSI using the GetAnsi() method exposed by the CString class.
return ORIGINAL_API(CreateProcessA)( csNewApplicationName.IsEmpty() ? NULL : csNewApplicationName.GetAnsi(), csNewCommandLine.IsEmpty() ? NULL : csNewCommandLine.GetAnsi(), lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
} CSTRING_CATCH { DPFN( eDbgLevelError, "[CreateProcessA]:Original API called.Exception occured!"); }
exit: return ORIGINAL_API(CreateProcessA)(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); }
/*++
Hooks the CreateProcessW function to see if any News need to be substituted.
--*/
BOOL APIHOOK(CreateProcessW)( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { if ((NULL == lpApplicationName) && (NULL == lpCommandLine)) { // If both are NULL, return FALSE.
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
CSTRING_TRY { CString csNewApplicationName; CString csNewCommandLine; CString csApplicationName(lpApplicationName); CString csCommandLine(lpCommandLine);
if ((csApplicationName.IsEmpty()) && (csCommandLine.IsEmpty())) { goto exit; }
//
// Run the list of New stubs
//
if (Redirect(csApplicationName, csCommandLine, csNewApplicationName, csNewCommandLine, FALSE)) { LOGN( eDbgLevelWarning, "[CreateProcessW] \" %S %S \": changed to \" %S %S \"", lpApplicationName, lpCommandLine, csNewApplicationName, csNewCommandLine); } else { csNewApplicationName = lpApplicationName; csNewCommandLine = lpCommandLine; }
return ORIGINAL_API(CreateProcessW)( csNewApplicationName.IsEmpty() ? NULL : csNewApplicationName.Get(), csNewCommandLine.IsEmpty() ? NULL : (LPWSTR)csNewCommandLine.Get(), lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } CSTRING_CATCH { DPFN( eDbgLevelError, "[CreateProcessW] Original API called. Exception occured!"); }
exit: return ORIGINAL_API(CreateProcessW)(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); }
/*++
Hooks WinExec to redirect if necessary.
--*/
UINT APIHOOK(WinExec)( LPCSTR lpCmdLine, UINT uCmdShow ) { if (NULL == lpCmdLine) { SetLastError(ERROR_INVALID_PARAMETER); return ERROR_PATH_NOT_FOUND; }
CSTRING_TRY { CString csNewApplicationName; CString csNewCommandLine; CString csAppName; CString csNewCmdLine; CString csCommandLine(lpCmdLine); if (csCommandLine.IsEmpty()) { goto exit; } // Check for redirection
if (Redirect(csAppName, csCommandLine, csNewApplicationName, csNewCommandLine, FALSE)) { // Modification for the WinHlp32 strange behaviour
if (csNewCommandLine.Find(csNewApplicationName.Get()) == -1) { // If the new Command line does not contain the new application
// name as the substring, we are here.
csNewCmdLine = csNewApplicationName; csNewCmdLine += L" "; } csNewCmdLine += csNewCommandLine;
// Assign to csCommandLine as this can be commonly used
csCommandLine = csNewCmdLine;
LOGN( eDbgLevelInfo, "[WinExec] \" %s \": changed to \" %s \"", lpCmdLine, csCommandLine.GetAnsi()); }
return ORIGINAL_API(WinExec)(csCommandLine.GetAnsi(), uCmdShow);
} CSTRING_CATCH { DPFN( eDbgLevelError, "[WinExec]:Original API called.Exception occured!"); }
exit: return ORIGINAL_API(WinExec)(lpCmdLine, uCmdShow); }
/*++
Hooks the FindFirstFileA function to see if any replacements need to be substituted. This is a requirement for cmd.exe.
--*/
HANDLE APIHOOK(FindFirstFileA)( LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData ) { CSTRING_TRY { CString csNewApplicationName; CString csNewCommandLine; CString csFileName(lpFileName); CString csAppName;
// Call the main replacement routine.
if (Redirect(csFileName, csAppName, csNewApplicationName, csNewCommandLine, FALSE)) { // Assign to csFileName
csFileName = csNewApplicationName; LOGN( eDbgLevelInfo, "[FindFirstFileA] \" %s \": changed to \" %s \"", lpFileName, csFileName.GetAnsi()); }
return ORIGINAL_API(FindFirstFileA)(csFileName.GetAnsi(), lpFindFileData); } CSTRING_CATCH { DPFN( eDbgLevelError, "[FindFirstFileA]:Original API called.Exception occured!"); return ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData); } }
/*++
Hooks the FindFirstFileW function to see if any replacements need to be substituted. This is a requirement for cmd.exe.
--*/
HANDLE APIHOOK(FindFirstFileW)( LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData ) { CSTRING_TRY { CString csNewApplicationName(lpFileName); CString csNewCommandLine; CString csFileName(lpFileName); CString csAppName; // Call the main replacement routine.
if (Redirect(csFileName, csAppName, csNewApplicationName, csNewCommandLine, FALSE)) { LOGN( eDbgLevelInfo, "[FindFirstFileW] \" %S \": changed to \" %S \"", lpFileName, (const WCHAR*)csNewApplicationName); }
return ORIGINAL_API(FindFirstFileW)(csNewApplicationName, lpFindFileData); } CSTRING_CATCH { DPFN( eDbgLevelError, "[FindFirstFileW]:Original API called.Exception occured!"); return ORIGINAL_API(FindFirstFileW)(lpFileName, lpFindFileData); } }
// Added for the merge of HandleStartKeyword
/*++
Hook IShellLinkA::SetPath - check if it's start, if so change it to cmd and add the this pointer to the list.
--*/
HRESULT STDMETHODCALLTYPE COMHOOK(IShellLinkA, SetPath)( PVOID pThis, LPCSTR pszFile ) { _pfn_IShellLinkA_SetPath pfnSetPath = ORIGINAL_COM( IShellLinkA, SetPath, pThis);
CSTRING_TRY { CString csExeName; CString csCmdLine; CString csNewAppName; CString csNewCmdLine; CString cscmdCommandLine(pszFile);
// Assign the ANSI string to the WCHAR CString
csExeName = pszFile; csExeName.TrimLeft(); // Check to see whether the Filename conatains the "Start" keyword.
// The last parameter to the Rediect function controls this.
if (Redirect(csExeName, csCmdLine, csNewAppName, csNewCmdLine, TRUE)) { // Found a match. We add the this pointer to the list.
AddThisPointer(pThis); DPFN( eDbgLevelInfo, "[SetPath] Changing start.exe to cmd.exe\n");
// Prefix of new "start" command line, use full path to CMD.EXE
// Append the WCHAR global system directory path to ANSI CString
cscmdCommandLine = g_szSysDir; cscmdCommandLine += L"\\cmd.exe"; }
return (*pfnSetPath)(pThis, cscmdCommandLine.GetAnsi()); } CSTRING_CATCH { DPFN( eDbgLevelError, "[SetPath] Original API called. Exception occured!"); return (*pfnSetPath)(pThis, pszFile); } }
/*++
Hook IShellLinkA::SetArguments - if the this pointer can be found in the list, remove it from the list and add "/d /c start" in front of the original argument list.
--*/
HRESULT STDMETHODCALLTYPE COMHOOK(IShellLinkA, SetArguments)( PVOID pThis, LPCSTR pszFile ) { _pfn_IShellLinkA_SetArguments pfnSetArguments = ORIGINAL_COM(IShellLinkA, SetArguments, pThis);
CSTRING_TRY { CString csNewFile(pszFile); if (RemoveThisPointer(pThis)) { csNewFile = "/d /c start \"\" "; csNewFile += pszFile;
DPFN( eDbgLevelInfo, "[SetArguments] Arg list is now %S", csNewFile); }
return (*pfnSetArguments)( pThis, csNewFile.GetAnsi()); } CSTRING_CATCH { DPFN( eDbgLevelError, "[SetArguments]:Original API called.Exception occured!"); return (*pfnSetArguments)( pThis, pszFile ); } }
/*++
Register hooked functions
--*/
BOOL NOTIFY_FUNCTION( DWORD fdwReason ) { if (fdwReason == DLL_PROCESS_ATTACH) { if (!GetSystemDirectory(g_szSysDir, MAX_PATH)) { DPFN( eDbgLevelError, "[Notify] GetSystemDirectory failed"); return FALSE; }
InitializeCriticalSection(&g_CritSec); } return TRUE; }
HOOK_BEGIN
CALL_NOTIFY_FUNCTION
APIHOOK_ENTRY(KERNEL32.DLL, CreateProcessA) APIHOOK_ENTRY(KERNEL32.DLL, CreateProcessW) APIHOOK_ENTRY(KERNEL32.DLL, WinExec) APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileA) APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileW) APIHOOK_ENTRY_COMSERVER(SHELL32) COMHOOK_ENTRY(ShellLink, IShellLinkA, SetPath, 20) COMHOOK_ENTRY(ShellLink, IShellLinkA, SetArguments, 11)
HOOK_END
IMPLEMENT_SHIM_END
|