/*++ Copyright (c) 2000 Microsoft Corporation Module Name: WoWTask.cpp Abstract: Functions that retrieve process-history related information from 16-bit environment. This includes the retrieval of the correct __PROCESS_HISTORY that was passed in from the parent (32-bit)process and tracing the process history through WOW Notes: History: 10/26/00 VadimB Created --*/ #include "precomp.h" // This module has been given an official blessing to use the str routines. #include "LegalStr.h" IMPLEMENT_SHIM_BEGIN(Win2kPropagateLayer) #include "ShimHookMacro.h" #include "Win2kPropagateLayer.h" typedef struct tagFINDWOWTASKDATA { BOOL bFound; DWORD dwProcessId; DWORD dwThreadId; WORD hMod16; WORD hTask16; } FINDWOWTASKDATA, *PFINDWOWTASKDATA; // // Dynamically Linked apis // // from WOW32.dll // typedef LPVOID (WINAPI *PFNWOWGetVDMPointer)(DWORD vp, DWORD dwBytes, BOOL fProtectedMode); // // from vdmdbg.dll - defined in the header file // // typedef INT (WINAPI *PFNVDMEnumTaskWOW)(DWORD dwProcessId, TASKENUMPROC fp, LPARAM lparam); // // Api importing -- modules // WCHAR g_wszWOW32ModName[] = L"wow32.dll"; WCHAR g_wszVdmDbgModName[] = L"VdmDbg.dll"; // // Api importing - module handles and function pointers // HMODULE g_hWow32; HMODULE g_hVdmDbg; BOOL g_bInitialized; // set to true when imports are initialized PFNWOWGetVDMPointer g_pfnWOWGetVDMPointer; PFNVDMEnumTaskWOW g_pfnVDMEnumTaskWOW; extern BOOL* g_pSeparateWow; // // function in this module to import apis // BOOL ImportWowApis(VOID); // // Marcro to access 16-bit memory // #define SEGPTR(seg,off) ((g_pfnWOWGetVDMPointer)((((ULONG)seg) << 16) | (off), 0, TRUE)) // // task enum proc, called back from vdmdbg // BOOL WINAPI MyTaskEnumProc( DWORD dwThreadId, WORD hMod16, WORD hTask16, LPARAM lParam ) { PFINDWOWTASKDATA pFindData = (PFINDWOWTASKDATA)lParam; if (dwThreadId == pFindData->dwThreadId) { pFindData->hMod16 = hMod16; pFindData->hTask16 = hTask16; pFindData->bFound = TRUE; return TRUE; } return FALSE; } BOOL FindWowTask( DWORD dwProcessId, DWORD dwThreadId, PFINDWOWTASKDATA pFindData ) { RtlZeroMemory(pFindData, sizeof(*pFindData)); pFindData->dwProcessId = dwProcessId; pFindData->dwThreadId = dwThreadId; g_pfnVDMEnumTaskWOW(dwProcessId, (TASKENUMPROC)MyTaskEnumProc, (LPARAM)pFindData); return pFindData->bFound; } // // get the pointer to task database block from hTask // PTDB GetTDB( WORD wTDB ) { PTDB pTDB; pTDB = (PTDB)SEGPTR(wTDB, 0); if (NULL == pTDB || TDB_SIGNATURE != pTDB->TDB_sig) { LOGN( eDbgLevelError, "[GetTDB] TDB is invalid for task 0x%x", (DWORD)wTDB); return NULL; } return pTDB; } // // GetModName // wTDB - TDB entry // szModName - pointer to the buffer that receives module name // buffer should be at least 9 characters long // // returns FALSE if the entry is invalid BOOL GetModName( WORD wTDB, PCH szModName ) { PTDB pTDB; PCH pch; pTDB = GetTDB(wTDB); if (NULL == pTDB) { return FALSE; } RtlCopyMemory(szModName, pTDB->TDB_ModName, 8 * sizeof(CHAR)); // we have modname now szModName[8] = '\0'; pch = &szModName[8]; while (*(--pch) == ' ') { *pch = 0; } return TRUE; } // // ShimGetTaskFileName // IN wTask - 16-bit task handle // Returns: // Fully qualified exe that is running in this task's context // PSZ ShimGetTaskFileName( WORD wTask ) { PSZ pszFileName = NULL; PTDB pTDB; pTDB = GetTDB(wTask); if (NULL == pTDB) { // this is really bad -- the module is invalid, debug output is generated by GetTDB return pszFileName; } if (NULL == pTDB->TDB_pModule) { LOGN( eDbgLevelError, "[ShimGetTaskFileName] module pointer is NULL for 0x%x", (DWORD)wTask); return pszFileName; } pszFileName = (PSZ)SEGPTR(pTDB->TDB_pModule, (*(WORD *)SEGPTR(pTDB->TDB_pModule, 10)) + 8); return pszFileName; } PSZ ShimGetTaskEnvptr( WORD hTask16 ) { PTDB pTDB = GetTDB(hTask16); PSZ pszEnv = NULL; PDOSPDB pPSP; if (NULL == pTDB) { LOGN( eDbgLevelError, "[ShimGetTaskEnvptr] Bad TDB entry 0x%x", hTask16); return NULL; } // // Prepare environment data - this buffer is used when we're starting a new task from the // root of the chain (as opposed to spawning from an existing 16-bit task) // pPSP = (PDOSPDB)SEGPTR(pTDB->TDB_PDB, 0); // psp if (pPSP != NULL) { pszEnv = (PCH)SEGPTR(pPSP->PDB_environ, 0); } return pszEnv; } // IsWowExec // IN wTDB - entry into the task database // Returns: // TRUE if this particular entry points to WOWEXEC // // Note: // WOWEXEC is a special stub module that always runs on NTVDM // new tasks are spawned by wowexec (in the most typical case) // it is therefore the "root" module and it's environment's contents // should not be counted, since we don't know what was ntvdm's parent process // BOOL IsWOWExec( WORD wTDB ) { PTDB pTDB; PTDB pTDBParent; CHAR szModName[9]; pTDB = GetTDB(wTDB); if (NULL == pTDB) { LOGN( eDbgLevelError, "[IsWOWExec] Bad TDB entry 0x%x", (DWORD)wTDB); return FALSE; } if (!GetModName(wTDB, szModName)) { // can we get modname ? LOGN( eDbgLevelError, "[IsWOWExec] GetModName failed."); return FALSE; } return (0 == _strcmpi(szModName, "wowexec")); // is the module named WOWEXEC ? } // // ImportWowApis // Function imports necessary apis from wow32.dll and vdmdbg.dll // // BOOL ImportWowApis( VOID ) { g_hWow32 = LoadLibraryW(g_wszWOW32ModName); if (g_hWow32 == NULL) { LOGN( eDbgLevelError, "[ImportWowApis] Failed to load wow32.dll Error 0x%x", GetLastError()); goto Fail; } g_pfnWOWGetVDMPointer = (PFNWOWGetVDMPointer)GetProcAddress(g_hWow32, "WOWGetVDMPointer"); if (g_pfnWOWGetVDMPointer == NULL) { LOGN( eDbgLevelError, "[ImportWowApis] Failed to get address of WOWGetVDMPointer Error 0x%x", GetLastError()); goto Fail; } g_hVdmDbg = LoadLibraryW(g_wszVdmDbgModName); if (g_hVdmDbg == NULL) { LOGN( eDbgLevelError, "[ImportWowApis] Failed to load vdmdbg.dll Error 0x%x", GetLastError()); goto Fail; } g_pfnVDMEnumTaskWOW = (PFNVDMEnumTaskWOW)GetProcAddress(g_hVdmDbg, "VDMEnumTaskWOW"); if (g_pfnVDMEnumTaskWOW == NULL) { LOGN( eDbgLevelError, "[ImportWowApis] Failed to get address of VDMEnumTaskWOW Error 0x%x", GetLastError()); goto Fail; } g_bInitialized = TRUE; return TRUE; Fail: if (g_hWow32) { FreeLibrary(g_hWow32); g_hWow32 = NULL; } if (g_hVdmDbg) { FreeLibrary(g_hVdmDbg); g_hVdmDbg = NULL; } g_pfnWOWGetVDMPointer = NULL; g_pfnVDMEnumTaskWOW = NULL; return FALSE; } ///////////////////////////////////////////////////////////////////////////////////////////// // // // WOWTaskList // // We maintain a shadow list of running wow tasks complete with respective process history and // inherited process history // // typedef struct tagWOWTASKLISTITEM* PWOWTASKLISTITEM; typedef struct tagWOWTASKLISTITEM { WORD hTask16; // 16-bit tdb entry DWORD dwThreadId; // thread id of the task WOWENVDATA EnvData; // environment data (process history, compat layer, etc) PWOWTASKLISTITEM pTaskNext; } WOWTASKLISTITEM; PWOWTASKLISTITEM g_pWowTaskList; /*++ FindWowTaskInfo IN hTask16 16-bit task's handle IN dwThreadId OPTIONAL 32-bit thread id of the task, might be 0 Returns: pointer to the task information structure --*/ PWOWTASKLISTITEM FindWowTaskInfo( WORD hTask16, DWORD dwThreadId ) { PWOWTASKLISTITEM pTask = g_pWowTaskList; while (NULL != pTask) { if (hTask16 == pTask->hTask16) { if (dwThreadId == 0 || dwThreadId == pTask->dwThreadId) { break; } } pTask = pTask->pTaskNext; } return pTask; } /*++ UpdateWowTaskList IN hTask16 16-bit task's handle Returns: True if the task was added successfully Note: wowexec is not among the "legitimate" tasks --*/ BOOL UpdateWowTaskList( WORD hTask16 ) { PTDB pTDB; WORD wTaskParent; PWOWTASKLISTITEM pTaskParent = NULL; LPSTR lpszFileName; PSZ pszEnv; WOWENVDATA EnvData; PWOWENVDATA pData = NULL; DWORD dwLength; PWOWTASKLISTITEM pTaskNew; PCH pBuffer; PDOSPDB pPSP; BOOL bSuccess; // // see that we are initialized, import apis // if (!g_bInitialized) { // first call, link apis bSuccess = ImportWowApis(); if (!bSuccess) { LOGN( eDbgLevelError, "[UpdateWowTaskList] Failed to import apis."); return FALSE; } } // // If this task is WOWEXEC -- just return, it's not an error condition, but we don't need // wowexec in our list // if (IsWOWExec(hTask16)) { // this is ok, we don't want wowexec return FALSE; } // // next, see what the parent item is, to do so -- access it through TDB // pTDB = GetTDB(hTask16); if (NULL == pTDB) { LOGN( eDbgLevelError, "[UpdateWowTaskList] Bad TDB entry 0x%x", hTask16); return FALSE; } // // Prepare environment data - this buffer is used when we're starting a new task from the // root of the chain (as opposed to spawning from an existing 16-bit task) // RtlZeroMemory(&EnvData, sizeof(EnvData)); pData = &EnvData; wTaskParent = pTDB->TDB_Parent; if (IsWOWExec(wTaskParent) || GetTDB(wTaskParent) == NULL) { // // Root task, extract process history, compat layer, etc // pszEnv = NULL; pPSP = (PDOSPDB)SEGPTR(pTDB->TDB_PDB, 0); // psp if (pPSP != NULL) { pszEnv = (PCH)SEGPTR(pPSP->PDB_environ, 0); } // // we have a pointer to the current environment here, pData is initialized // if (pszEnv != NULL) { pData->pszProcessHistory = ShimFindEnvironmentVar(g_szProcessHistoryVar, pszEnv, &pData->pszProcessHistoryVal); pData->pszCompatLayer = ShimFindEnvironmentVar(g_szCompatLayerVar, pszEnv, &pData->pszCompatLayerVal); pData->pszShimFileLog = ShimFindEnvironmentVar(g_szShimFileLogVar, pszEnv, &pData->pszShimFileLogVal); } } else { // // Not a root task, find parent process // pTaskParent = FindWowTaskInfo(wTaskParent, 0); // we can't determine which thread owns the task if (pTaskParent == NULL) { // // something is very wrong // we can't inherit // LOGN( eDbgLevelError, "[UpdateWowTaskList] Task 0x%x is not root but parent not listed 0x%x", (DWORD)hTask16, (DWORD)wTaskParent); // // we still allow building up process history. The initial variables will be empty // } else { // // inherit everything from the parent and add it's module name (later) // pData = &pTaskParent->EnvData; } } // // Get the filename involved // // lpszFileName = ShimGetTaskFileName(hTask16); // // now calculate how much space is required to hold all of the data // dwLength = sizeof(WOWTASKLISTITEM) + (NULL == pData->pszProcessHistory ? 0 : (strlen(pData->pszProcessHistory) + 1) * sizeof(CHAR)) + (NULL == pData->pszCompatLayer ? 0 : (strlen(pData->pszCompatLayer) + 1) * sizeof(CHAR)) + (NULL == pData->pszShimFileLog ? 0 : (strlen(pData->pszShimFileLog) + 1) * sizeof(CHAR)) + (NULL == pData->pszCurrentProcessHistory ? 0 : (strlen(pData->pszCurrentProcessHistory) + 1) * sizeof(CHAR)) + (NULL == lpszFileName ? 0 : (strlen(lpszFileName) + 1) * sizeof(CHAR)); pTaskNew = (PWOWTASKLISTITEM)ShimMalloc(dwLength); if (pTaskNew == NULL) { LOGN( eDbgLevelError, "[UpdateWowTaskList] failed to allocate 0x%x bytes", dwLength); return FALSE; } RtlZeroMemory(pTaskNew, dwLength); // // now this entry has to be setup // process history is first // pBuffer = (PCH)(pTaskNew + 1); pTaskNew->hTask16 = hTask16; pTaskNew->dwThreadId = GetCurrentThreadId(); if (pData->pszProcessHistory != NULL) { // // Copy process history. The processHistoryVal is a pointer into the buffer // pointed to by pszProcessHistory: __PROCESS_HISTORY=c:\foo;c:\docs~1\install // then pszProcessHistoryVal will point here ---------^ // // we are copying the data and moving the pointer using the calculated offset pTaskNew->EnvData.pszProcessHistory = pBuffer; strcpy(pTaskNew->EnvData.pszProcessHistory, pData->pszProcessHistory); pTaskNew->EnvData.pszProcessHistoryVal = pTaskNew->EnvData.pszProcessHistory + (INT)(pData->pszProcessHistoryVal - pData->pszProcessHistory); // // There is enough space in the buffer to accomodate all the strings, so // move pointer past current string to point at the "empty" space // pBuffer += strlen(pData->pszProcessHistory) + 1; } if (pData->pszCompatLayer != NULL) { pTaskNew->EnvData.pszCompatLayer = pBuffer; strcpy(pTaskNew->EnvData.pszCompatLayer, pData->pszCompatLayer); pTaskNew->EnvData.pszCompatLayerVal = pTaskNew->EnvData.pszCompatLayer + (INT)(pData->pszCompatLayerVal - pData->pszCompatLayer); pBuffer += strlen(pData->pszCompatLayer) + 1; } if (pData->pszShimFileLog != NULL) { pTaskNew->EnvData.pszShimFileLog = pBuffer; strcpy(pTaskNew->EnvData.pszShimFileLog, pData->pszShimFileLog); pTaskNew->EnvData.pszShimFileLogVal = pTaskNew->EnvData.pszShimFileLog + (INT)(pData->pszShimFileLogVal - pData->pszShimFileLog); pBuffer += strlen(pData->pszShimFileLog) + 1; } if (pData->pszCurrentProcessHistory != NULL || lpszFileName != NULL) { // // Now process history // pTaskNew->EnvData.pszCurrentProcessHistory = pBuffer; if (pData->pszCurrentProcessHistory != NULL) { strcpy(pTaskNew->EnvData.pszCurrentProcessHistory, pData->pszCurrentProcessHistory); strcat(pTaskNew->EnvData.pszCurrentProcessHistory, ";"); } if (lpszFileName != NULL) { strcat(pTaskNew->EnvData.pszCurrentProcessHistory, lpszFileName); } } LOGN( eDbgLevelInfo, "[UpdateWowTaskList] Running : \"%s\"", lpszFileName); LOGN( eDbgLevelInfo, "[UpdateWowTaskList] ProcessHistory : \"%s\"", pTaskNew->EnvData.pszCurrentProcessHistory); LOGN( eDbgLevelInfo, "[UpdateWowTaskList] BaseProcessHistory: \"%s\"", pTaskNew->EnvData.pszProcessHistory); LOGN( eDbgLevelInfo, "[UpdateWowTaskList] CompatLayer : \"%s\"", pTaskNew->EnvData.pszCompatLayer); // // We are done, link the entry into the list // pTaskNew->pTaskNext = g_pWowTaskList; g_pWowTaskList = pTaskNew; return TRUE; } /*++ CleanupWowTaskList IN hTask16 16-bit task handle that is to be removed from the list of running tasks Returns : TRUE if the function succeeds --*/ BOOL CleanupWowTaskList( WORD hTask16 ) { PWOWTASKLISTITEM pTask = g_pWowTaskList; PWOWTASKLISTITEM pTaskPrev = NULL; while (pTask != NULL) { if (pTask->hTask16 == hTask16) { // this is the item break; } pTaskPrev = pTask; pTask = pTask->pTaskNext; } if (pTask == NULL) { LOGN( eDbgLevelError, "[CleanupWowTaskList] Failed to locate task information for 0x%x", (DWORD)hTask16); return FALSE; } if (pTaskPrev == NULL) { g_pWowTaskList = pTask->pTaskNext; } else { pTaskPrev->pTaskNext = pTask->pTaskNext; } ShimFree(pTask); return TRUE; } /*++ ShimRetrieveVariablesEx IN pData Structure that receives pointers to all the relevant environment information for the calling thread. The threads are scheduled non-preemptively by user and threadid is used to identify the calling 16-bit task All the real work on information retrieval is done in UpdateWowTaskList Returns: TRUE if success --*/ BOOL ShimRetrieveVariablesEx( PWOWENVDATA pData ) { DWORD dwProcessId = GetCurrentProcessId(); DWORD dwThreadId = GetCurrentThreadId(); PWOWTASKLISTITEM pTask; FINDWOWTASKDATA FindData; WORD hTask; BOOL bSuccess; RtlZeroMemory(pData, sizeof(*pData)); if (!g_bInitialized) { // first call, link apis bSuccess = ImportWowApis(); if (!bSuccess) { LOGN( eDbgLevelError, "[ShimRetrieveVariablesEx] Failed to import apis."); return FALSE; } } if (!FindWowTask(dwProcessId, dwThreadId, &FindData)) { LOGN( eDbgLevelError, "[ShimRetrieveVariablesEx] Task not found ProcessId 0x%x ThreadId 0x%x", dwProcessId, dwThreadId); return FALSE; } hTask = FindData.hTask16; pTask = FindWowTaskInfo(hTask, dwThreadId); if (pTask == NULL) { LOGN( eDbgLevelError, "[ShimRetrieveVariablesEx] Failed to locate wow task."); return FALSE; } // // Found this one. Copy the info. // RtlMoveMemory(pData, &pTask->EnvData, sizeof(*pData)); return TRUE; } /*++ ShimThisProcess Function invokes Shim Engine for dynamic shimming of the current process Which happens to be ntvdm, naturally. This ntvdm is a separate ntvdm (which is insured through various checks in CheckAndShimNTVDM) --*/ BOOL ShimThisProcess( HMODULE hModShimEngine, HSDB hSDB, SDBQUERYRESULT* pQueryResult ) { typedef BOOL (WINAPI *PFNDynamicShim)(LPCWSTR , HSDB , SDBQUERYRESULT*, LPCSTR); PFNDynamicShim pfnDynamicShim = NULL; WCHAR wszFileName[MAX_PATH]; DWORD dwLength; pfnDynamicShim = (PFNDynamicShim) GetProcAddress(hModShimEngine, "SE_DynamicShim"); if (NULL == pfnDynamicShim) { LOGN( eDbgLevelError, "[ShimThisProcess] failed to obtain dynamic shim proc address\n"); return FALSE; } dwLength = GetModuleFileNameW(GetModuleHandle(NULL), wszFileName, CHARCOUNT(wszFileName)); if (!dwLength || dwLength > CHARCOUNT(wszFileName)) { LOGN( eDbgLevelError, "[ShimThisProcess] failed to obtain module file name\n"); return FALSE; } return pfnDynamicShim(wszFileName, hSDB, pQueryResult, NULL); } /*++ CheckAndShimNTVDM Procedure checks ntvdm application for having to be shimmed. If an application is located in appcompat database, this ntvdm would have to be running as a separate ntvdm (explorer is shimmed as well, as a result it will have checked the binary first and set the separate vdm flag in CreateProcess) Further, this call comes through InitTask (intercepted between ntvdm and user32) -- as a parameter it takes hTask16 - which we're able to use to retrieve application's environment and other important information. --*/ BOOL CheckAndShimNTVDM( WORD hTask16 ) { HMODULE hModShimEngine; PSZ pszTaskFileName = NULL; CString csTaskFileName; DWORD dwExeCount; PSZ pszEnv = NULL; PTDB pTDB = NULL; PVOID pEnvNew = NULL; PDOSPDB pPSP; BOOL bSuccess = FALSE; BOOL bMatch; BOOL bNewEnv = FALSE; HSDB hSDB; NTSTATUS Status; SDBQUERYRESULT QueryResult; DWORD dwFlags; hModShimEngine = GetModuleHandle(TEXT("shim.dll")); if (hModShimEngine == NULL) { // impossible -- shim.dll is not injected!!! return FALSE; } if (g_pSeparateWow != NULL && *g_pSeparateWow == FALSE) { // // not a separate wow // LOGN( eDbgLevelError, "[CheckAndShimNTVDM] running in shared wow, no shimming\n"); return FALSE; } if (!g_bInitialized) { // first call, link apis bSuccess = ImportWowApis(); if (!bSuccess) { LOGN( eDbgLevelError, "[CheckAndShimNTVDM] Failed to import apis.\n"); return FALSE; } } if (IsWOWExec(hTask16)) { LOGN( eDbgLevelError, "[CheckAndShimNTVDM] not touching wowexec\n"); return FALSE; } csTaskFileName = ShimGetTaskFileName(hTask16); if (csTaskFileName.IsEmpty()) { LOGN( eDbgLevelError, "[CheckAndShimNTVDM] failed to get the filename for task 0x%lx\n", hTask16); return FALSE; } // // init database // hSDB = SdbInitDatabase(0, NULL); if (hSDB == NULL) { LOGN( eDbgLevelError, "[CheckAndShimNTVDM] failed to init shim database\n"); return FALSE; } // // process history please -- // if we end up here, we are a separate ntvdm // running with a process history in the env, was retrieved in init // pTDB = GetTDB(hTask16); if (NULL == pTDB) { LOGN( eDbgLevelError, "[UpdateWowTaskList] Bad TDB entry 0x%x", hTask16); return FALSE; } // // Prepare environment data - this buffer is used when we're starting a new task from the // root of the chain (as opposed to spawning from an existing 16-bit task) // pszEnv = ShimGetTaskEnvptr(hTask16); if (NULL != pszEnv) { Status = ShimCloneEnvironment(&pEnvNew, (LPVOID)pszEnv, FALSE); if (!NT_SUCCESS(Status)) { LOGN( eDbgLevelError, "[CheckAndShimNTVDM] cannot clone environment 0x%lx\n", Status); pEnvNew = NULL; bNewEnv = TRUE; } // // if this call has come the way of VDM - we need to carry over our environment stuff // which is stored separately in this shim // // should the call to ShimCloneEnvironment fail, we will have pEnvNew == NULL // and bNewEnv = TRUE, as a result, we shall try again to clone the environment dwFlags = CREATE_UNICODE_ENVIRONMENT; pEnvNew = ShimCreateWowEnvironment_U(pEnvNew, &dwFlags, bNewEnv); } // // run detection please // bMatch = SdbGetMatchingExe(hSDB, (LPCWSTR)csTaskFileName, NULL, // we can give out module name as well -- but WHY? (LPCWSTR)pEnvNew, 0, &QueryResult); if (bMatch) { bSuccess = ShimThisProcess(hModShimEngine, hSDB, &QueryResult); } if (pEnvNew != NULL) { ShimFreeEnvironment(pEnvNew); } return bSuccess; } IMPLEMENT_SHIM_END