BOOL TlsAddCurrentThread(THREAD_LOCAL_STORAGE* InTls) { /* Description: Tries to reserve a THREAD_RUNTIME_INFO entry for the calling thread. On success it may call TlsGetCurrentValue() to query a pointer to its private entry. This is a replacement for the Windows Thread Local Storage which seems to cause trouble when using it in Explorer.EXE for example. No parameter validation (for performance reasons). This method will raise an assertion if the thread was already added to the storage! Parameters: - InTls The thread local storage to allocate from. Returns: TRUE on success, FALSE otherwise. */ #ifndef DRIVER ULONG CurrentId = (ULONG)GetCurrentThreadId(); #else ULONG CurrentId = (ULONG)PsGetCurrentThreadId(); #endif LONG Index = -1; LONG i; RtlAcquireLock(&InTls->ThreadSafe); // select Index AND check whether thread is already registered. for(i = 0; i < MAX_THREAD_COUNT; i++) { if((InTls->IdList[i] == 0) && (Index == -1)) Index = i; ASSERT(InTls->IdList[i] != CurrentId); } if(Index == -1) { RtlReleaseLock(&InTls->ThreadSafe); return FALSE; } InTls->IdList[Index] = CurrentId; RtlZeroMemory(&InTls->Entries[Index], sizeof(THREAD_RUNTIME_INFO)); RtlReleaseLock(&InTls->ThreadSafe); return TRUE; }
void TlsRemoveCurrentThread(THREAD_LOCAL_STORAGE* InTls) { /* Description: Removes the caller from the local storage. If the caller is already removed, the method will do nothing. Parameters: - InTls The storage from which the caller should be removed. */ #ifndef DRIVER ULONG CurrentId = (ULONG)GetCurrentThreadId(); #else ULONG CurrentId = (ULONG)PsGetCurrentThreadId(); #endif ULONG Index; RtlAcquireLock(&InTls->ThreadSafe); for(Index = 0; Index < MAX_THREAD_COUNT; Index++) { if(InTls->IdList[Index] == CurrentId) { InTls->IdList[Index] = 0; RtlZeroMemory(&InTls->Entries[Index], sizeof(THREAD_RUNTIME_INFO)); } } RtlReleaseLock(&InTls->ThreadSafe); }
EASYHOOK_NT_EXPORT LhUninstallAllHooks() { /* Description: Will remove ALL hooks. To also release associated resources, you will have to call LhWaitForPendingRemovals(). */ LOCAL_HOOK_INFO* Hook; LOCAL_HOOK_INFO* List; NTSTATUS NtStatus; RtlAcquireLock(&GlobalHookLock); { // remove from global list List = GlobalHookListHead.Next; while(List != NULL) { Hook = List; List = List->Next; // remove tracking if(LhIsValidHandle(Hook->Tracking, NULL)) { Hook->Tracking->Link = NULL; } // add to removal list Hook->HookProc = NULL; Hook->Next = GlobalRemovalListHead.Next; GlobalRemovalListHead.Next = Hook; } GlobalHookListHead.Next = NULL; } RtlReleaseLock(&GlobalHookLock); RETURN(STATUS_SUCCESS); FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_INTERNAL LhUpdateModuleInformation() { NTSTATUS NtStatus; PSYSTEM_MODULE_INFORMATION NativeList = NULL; ULONG RequiredSize = 0; ULONG i; PSYSTEM_MODULE Mod; MODULE_INFORMATION* List = NULL; if(!LhModuleListChanged) return STATUS_SUCCESS; LhModuleListChanged = FALSE; if(ZwQuerySystemInformation(11, NULL, 0, &RequiredSize) != STATUS_INFO_LENGTH_MISMATCH) THROW(STATUS_INTERNAL_ERROR, L"Unable to enumerate system modules."); if((NativeList = RtlAllocateMemory(TRUE, RequiredSize)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory."); if(!RTL_SUCCESS(ZwQuerySystemInformation(11, NativeList, RequiredSize, &RequiredSize))) THROW(STATUS_INTERNAL_ERROR, L"Unable to enumerate system modules."); if((List = RtlAllocateMemory(FALSE, sizeof(MODULE_INFORMATION) * NativeList->Count)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory."); for(i = 0; i < NativeList->Count; i++) { Mod = &NativeList->Modules[i]; List[i].BaseAddress = Mod->ImageBaseAddress; List[i].ImageSize = Mod->ImageSize; List[i].ModuleName = Mod->Name + Mod->NameOffset; memcpy(List[i].Path, Mod->Name, 256); if(i + 1 < NativeList->Count) List[i].Next = &List[i + 1]; else List[i].Next = NULL; } RtlAcquireLock(&GlobalHookLock); { if(LhNativeModuleArray != NULL) RtlFreeMemory(LhNativeModuleArray); if(LhModuleArray != NULL) RtlFreeMemory(LhModuleArray); LhNativeModuleArray = NativeList; LhModuleArray = List; LhModuleCount = NativeList->Count; } RtlReleaseLock(&GlobalHookLock); RETURN; THROW_OUTRO: { if(NativeList != NULL) RtlFreeMemory(NativeList); if(List != NULL) RtlFreeMemory(List); } FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_EXPORT LhBarrierPointerToModule( PVOID InPointer, MODULE_INFORMATION* OutModule) { /* Description: Translates the given pointer (likely a method) to its owning module if possible. Parameters: - InPointer A method pointer to be translated. - OutModule Receives the owner of a given method. Returns: STATUS_NOT_FOUND No matching module could be found. */ UCHAR* Pointer = (UCHAR*)InPointer; NTSTATUS NtStatus; BOOL CanTryAgain = TRUE; MODULE_INFORMATION* List; if(!IsValidPointer(OutModule, sizeof(MODULE_INFORMATION))) THROW(STATUS_INVALID_PARAMETER_2, L"The given module storage is invalid."); LABEL_TRY_AGAIN: RtlAcquireLock(&GlobalHookLock); { List = LhModuleArray; // walk through process modules while(List != NULL) { if((Pointer >= List->BaseAddress) && (Pointer <= List->BaseAddress + List->ImageSize)) { *OutModule = *List; RtlReleaseLock(&GlobalHookLock); RETURN; } List = List->Next; } } RtlReleaseLock(&GlobalHookLock); if((InPointer == NULL) || (InPointer == (PVOID)~0)) { // this pointer does not belong to any module... } else { // unable to find calling module... FORCE(LhUpdateModuleInformation()); if(CanTryAgain) { CanTryAgain = FALSE; goto LABEL_TRY_AGAIN; } } THROW(STATUS_NOT_FOUND, L"Unable to determine module."); THROW_OUTRO: FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_EXPORT LhEnumModules( HMODULE* OutModuleArray, ULONG InMaxModuleCount, ULONG* OutModuleCount) { /* Description: For performance reasons, only the module base addresses are returned. You may then loop through the array and use LhBarrierPointerToModule() to query each module information. Parameters: - OutModuleArray An array receiveing module pointers. Set to NULL to only query "OutModuleCount". - InMaxModuleCount The maximum count of modules that the given buffer can hold. - OutModuleCount The actual count of modules loaded into the current process or into the kernel, depending on the caller's context. This pointer must be specified if no module buffer is passed; if one is passed, this parameter is optional. */ ULONG ModIndex = 0; NTSTATUS NtStatus; MODULE_INFORMATION* List; if(IsValidPointer(OutModuleArray, InMaxModuleCount * sizeof(PVOID))) { // loop through the module list... RtlAcquireLock(&GlobalHookLock); { if(IsValidPointer(OutModuleCount, sizeof(ULONG))) *OutModuleCount = LhModuleCount; List = LhModuleArray; // walk through process modules while(List != NULL) { if(ModIndex > InMaxModuleCount) { RtlReleaseLock(&GlobalHookLock); THROW(STATUS_BUFFER_TOO_SMALL, L"The given buffer was filled but could not hold all modules."); } OutModuleArray[ModIndex++] = (HMODULE)List->BaseAddress; List = List->Next; } } RtlReleaseLock(&GlobalHookLock); } else { // return module count... if(!IsValidPointer(OutModuleCount, sizeof(ULONG))) THROW(STATUS_INVALID_PARAMETER_3, L"If no buffer is specified you need to pass a module count storage."); *OutModuleCount = LhModuleCount; } RETURN; THROW_OUTRO: FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_INTERNAL LhUpdateModuleInformation() { /* Description: Is supposed to be called interlocked... "ProcessModules" is outsourced to prevent "__chkstk". Will just enumerate current process modules and extract required information for each of them. */ LONG NtStatus = STATUS_UNHANDLED_EXCEPTION; ULONG Index; ULONG ModIndex; MODULEINFO* NativeList = NULL; ULONG ModuleCount; MODULEINFO* Mod = NULL; MODULE_INFORMATION* List = NULL; CHAR* PathList = NULL; CHAR* ModPath = NULL; ULONG ModPathSize = 0; LONG iChar = 0; // enumerate modules... RtlAcquireLock(&GlobalHookLock); { if(!EnumProcessModules( GetCurrentProcess(), ProcessModules, sizeof(ProcessModules), &ModuleCount)) { RtlReleaseLock(&GlobalHookLock); THROW(STATUS_INTERNAL_ERROR, L"Unable to enumerate current process modules."); } } RtlReleaseLock(&GlobalHookLock); ModuleCount /= sizeof(HMODULE); // retrieve module information if((NativeList = (MODULEINFO*)RtlAllocateMemory(FALSE, ModuleCount * sizeof(NativeList[0]))) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory for module information."); if((List = (MODULE_INFORMATION*)RtlAllocateMemory(TRUE, sizeof(MODULE_INFORMATION) * ModuleCount)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory."); if((PathList = (CHAR*)RtlAllocateMemory(TRUE, MAX_PATH * ModuleCount)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory."); for(Index = 0, ModIndex = 0; Index < ModuleCount; Index++) { // collect information if(!GetModuleInformation( GetCurrentProcess(), ProcessModules[Index], &NativeList[ModIndex], sizeof(NativeList[ModIndex]))) continue; GetModuleFileNameA( ProcessModules[Index], &PathList[ModIndex * MAX_PATH], MAX_PATH); if(GetLastError() != ERROR_SUCCESS) continue; Mod = &NativeList[ModIndex]; // normalize module information List[ModIndex].BaseAddress = (UCHAR*)Mod->lpBaseOfDll; List[ModIndex].ImageSize = Mod->SizeOfImage; memcpy(List[ModIndex].Path, &PathList[ModIndex * MAX_PATH], MAX_PATH + 1); ModPath = List[ModIndex].Path; ModPathSize = RtlAnsiLength(ModPath); for(iChar = ModPathSize; iChar >= 0; iChar--) { if(ModPath[iChar] == '\\') { List[ModIndex].ModuleName = &ModPath[iChar + 1]; break; } } if(ModIndex + 1 < ModuleCount) List[ModIndex].Next = &List[ModIndex + 1]; else List[ModIndex].Next = NULL; ModIndex++; } // save changes... RtlAcquireLock(&GlobalHookLock); { if(LhNativeModuleArray != NULL) RtlFreeMemory(LhNativeModuleArray); if(LhModuleArray != NULL) RtlFreeMemory(LhModuleArray); if(LhNativePathArray != NULL) RtlFreeMemory(LhNativePathArray); LhNativePathArray = PathList; LhNativeModuleArray = NativeList; LhModuleArray = List; LhModuleCount = ModIndex; } RtlReleaseLock(&GlobalHookLock); RETURN; THROW_OUTRO: { if(NativeList != NULL) RtlFreeMemory(NativeList); if(List != NULL) RtlFreeMemory(List); } FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_EXPORT LhUninstallHook(TRACED_HOOK_HANDLE InHandle) { /* Description: Removes the given hook. To also release associated resources, you will have to call LhWaitForPendingRemovals(). In any case your hook handler will never be executed again, after calling this method. Parameters: - InHandle A traced hook handle. If the hook is already removed, this method will still return STATUS_SUCCESS. */ LOCAL_HOOK_INFO* Hook = NULL; LOCAL_HOOK_INFO* List; LOCAL_HOOK_INFO* Prev; NTSTATUS NtStatus; BOOLEAN IsAllocated = FALSE; if(!IsValidPointer(InHandle, sizeof(HOOK_TRACE_INFO))) return FALSE; RtlAcquireLock(&GlobalHookLock); { if((InHandle->Link != NULL) && LhIsValidHandle(InHandle, &Hook)) { InHandle->Link = NULL; if(Hook->HookProc != NULL) { Hook->HookProc = NULL; IsAllocated = TRUE; } } if(!IsAllocated) { RtlReleaseLock(&GlobalHookLock); RETURN; } // remove from global list List = GlobalHookListHead.Next; Prev = &GlobalHookListHead; while(List != NULL) { if(List == Hook) { Prev->Next = Hook->Next; break; } List = List->Next; } // add to removal list Hook->Next = GlobalRemovalListHead.Next; GlobalRemovalListHead.Next = Hook; } RtlReleaseLock(&GlobalHookLock); RETURN(STATUS_SUCCESS); //THROW_OUTRO: FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_EXPORT LhWaitForPendingRemovals() { /* Descriptions: For stability reasons, all resources associated with a hook have to be released if no thread is currently executing the handler. Separating this wait loop from the uninstallation method is a great performance gain, because you can release all hooks first, and then wait for all removals simultaneously. */ PLOCAL_HOOK_INFO Hook; NTSTATUS NtStatus = STATUS_SUCCESS; INT32 Timeout = 1000; #ifdef X64_DRIVER KIRQL CurrentIRQL = PASSIVE_LEVEL; #endif #pragma warning(disable: 4127) while(TRUE) #pragma warning(default: 4127) { // pop from removal list RtlAcquireLock(&GlobalHookLock); { Hook = GlobalRemovalListHead.Next; if(Hook == NULL) { RtlReleaseLock(&GlobalHookLock); break; } GlobalRemovalListHead.Next = Hook->Next; } RtlReleaseLock(&GlobalHookLock); // restore entry point... if(Hook->HookCopy == *((ULONGLONG*)Hook->TargetProc)) { #ifdef X64_DRIVER CurrentIRQL = KeGetCurrentIrql(); RtlWPOff(); #endif *((ULONGLONG*)Hook->TargetProc) = Hook->TargetBackup; #ifdef X64_DRIVER // we support a trampoline jump of up to 16 bytes in X64_DRIVER *((ULONGLONG*)(Hook->TargetProc + 8)) = Hook->TargetBackup_x64; RtlWPOn(CurrentIRQL); #endif #pragma warning(disable: 4127) while (TRUE) #pragma warning(default: 4127) { if (*Hook->IsExecutedPtr <= 0) { // release slot if (GlobalSlotList[Hook->HLSIndex] == Hook->HLSIdent) { GlobalSlotList[Hook->HLSIndex] = 0; } // release memory... LhFreeMemory(&Hook); break; } if (Timeout <= 0) { // this hook was not released within timeout or cannot be released. // We will leak the memory, but not hang forever. NtStatus = STATUS_TIMEOUT; break; } RtlSleep(25); Timeout -= 25; } } else { // hook was changed... no chance to release resources } } return NtStatus; }