1763 lines
51 KiB
C
1763 lines
51 KiB
C
/*
|
|
* Process Hacker -
|
|
* symbol provider
|
|
*
|
|
* Copyright (C) 2010-2015 wj32
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <ph.h>
|
|
#include <workqueue.h>
|
|
#include <kphuser.h>
|
|
#include <fastlock.h>
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4091) // Ignore 'no variable declared on typedef'
|
|
#include <dbghelp.h>
|
|
#pragma warning(pop)
|
|
|
|
#include <symprv.h>
|
|
#include <symprvp.h>
|
|
|
|
typedef struct _PH_SYMBOL_MODULE
|
|
{
|
|
LIST_ENTRY ListEntry;
|
|
PH_AVL_LINKS Links;
|
|
ULONG64 BaseAddress;
|
|
ULONG Size;
|
|
PPH_STRING FileName;
|
|
ULONG BaseNameIndex;
|
|
} PH_SYMBOL_MODULE, *PPH_SYMBOL_MODULE;
|
|
|
|
VOID NTAPI PhpSymbolProviderDeleteProcedure(
|
|
_In_ PVOID Object,
|
|
_In_ ULONG Flags
|
|
);
|
|
|
|
VOID PhpRegisterSymbolProvider(
|
|
_In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider
|
|
);
|
|
|
|
VOID PhpFreeSymbolModule(
|
|
_In_ PPH_SYMBOL_MODULE SymbolModule
|
|
);
|
|
|
|
LONG NTAPI PhpSymbolModuleCompareFunction(
|
|
_In_ PPH_AVL_LINKS Links1,
|
|
_In_ PPH_AVL_LINKS Links2
|
|
);
|
|
|
|
PPH_OBJECT_TYPE PhSymbolProviderType;
|
|
|
|
static PH_INITONCE PhSymInitOnce = PH_INITONCE_INIT;
|
|
DECLSPEC_SELECTANY PH_CALLBACK_DECLARE(PhSymInitCallback);
|
|
|
|
static HANDLE PhNextFakeHandle = (HANDLE)0;
|
|
static PH_FAST_LOCK PhSymMutex = PH_FAST_LOCK_INIT;
|
|
|
|
#define PH_LOCK_SYMBOLS() PhAcquireFastLockExclusive(&PhSymMutex)
|
|
#define PH_UNLOCK_SYMBOLS() PhReleaseFastLockExclusive(&PhSymMutex)
|
|
|
|
_SymInitialize SymInitialize_I;
|
|
_SymCleanup SymCleanup_I;
|
|
_SymEnumSymbols SymEnumSymbols_I;
|
|
_SymEnumSymbolsW SymEnumSymbolsW_I;
|
|
_SymFromAddr SymFromAddr_I;
|
|
_SymFromAddrW SymFromAddrW_I;
|
|
_SymFromName SymFromName_I;
|
|
_SymFromNameW SymFromNameW_I;
|
|
_SymGetLineFromAddr64 SymGetLineFromAddr64_I;
|
|
_SymGetLineFromAddrW64 SymGetLineFromAddrW64_I;
|
|
_SymLoadModule64 SymLoadModule64_I;
|
|
_SymLoadModuleExW SymLoadModuleExW_I;
|
|
_SymGetOptions SymGetOptions_I;
|
|
_SymSetOptions SymSetOptions_I;
|
|
_SymGetSearchPath SymGetSearchPath_I;
|
|
_SymGetSearchPathW SymGetSearchPathW_I;
|
|
_SymSetSearchPath SymSetSearchPath_I;
|
|
_SymSetSearchPathW SymSetSearchPathW_I;
|
|
_SymUnloadModule64 SymUnloadModule64_I;
|
|
_SymFunctionTableAccess64 SymFunctionTableAccess64_I;
|
|
_SymGetModuleBase64 SymGetModuleBase64_I;
|
|
_SymRegisterCallbackW64 SymRegisterCallbackW64_I;
|
|
_StackWalk64 StackWalk64_I;
|
|
_MiniDumpWriteDump MiniDumpWriteDump_I;
|
|
_SymbolServerGetOptions SymbolServerGetOptions;
|
|
_SymbolServerSetOptions SymbolServerSetOptions;
|
|
|
|
BOOLEAN PhSymbolProviderInitialization(
|
|
VOID
|
|
)
|
|
{
|
|
PhSymbolProviderType = PhCreateObjectType(L"SymbolProvider", 0, PhpSymbolProviderDeleteProcedure);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID PhSymbolProviderCompleteInitialization(
|
|
_In_opt_ PVOID DbgHelpBase
|
|
)
|
|
{
|
|
HMODULE dbghelpHandle;
|
|
HMODULE symsrvHandle;
|
|
|
|
// The user should have loaded dbghelp.dll and symsrv.dll already. If not, it's not our problem.
|
|
|
|
// The Unicode versions aren't available in dbghelp.dll 5.1, so we fallback on the ANSI versions.
|
|
|
|
if (DbgHelpBase)
|
|
dbghelpHandle = DbgHelpBase;
|
|
else
|
|
dbghelpHandle = GetModuleHandle(L"dbghelp.dll");
|
|
|
|
symsrvHandle = GetModuleHandle(L"symsrv.dll");
|
|
|
|
SymInitialize_I = (PVOID)GetProcAddress(dbghelpHandle, "SymInitialize");
|
|
SymCleanup_I = (PVOID)GetProcAddress(dbghelpHandle, "SymCleanup");
|
|
if (!(SymEnumSymbolsW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymEnumSymbolsW")))
|
|
SymEnumSymbols_I = (PVOID)GetProcAddress(dbghelpHandle, "SymEnumSymbols");
|
|
if (!(SymFromAddrW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromAddrW")))
|
|
SymFromAddr_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromAddr");
|
|
if (!(SymFromNameW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromNameW")))
|
|
SymFromName_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromName");
|
|
if (!(SymGetLineFromAddrW64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetLineFromAddrW64")))
|
|
SymGetLineFromAddr64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetLineFromAddr64");
|
|
if (!(SymLoadModuleExW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymLoadModuleExW")))
|
|
SymLoadModule64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymLoadModule64");
|
|
SymGetOptions_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetOptions");
|
|
SymSetOptions_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetOptions");
|
|
if (!(SymGetSearchPathW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetSearchPathW")))
|
|
SymGetSearchPath_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetSearchPath");
|
|
if (!(SymSetSearchPathW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetSearchPathW")))
|
|
SymSetSearchPath_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetSearchPath");
|
|
SymUnloadModule64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymUnloadModule64");
|
|
SymFunctionTableAccess64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFunctionTableAccess64");
|
|
SymGetModuleBase64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetModuleBase64");
|
|
SymRegisterCallbackW64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymRegisterCallbackW64");
|
|
StackWalk64_I = (PVOID)GetProcAddress(dbghelpHandle, "StackWalk64");
|
|
MiniDumpWriteDump_I = (PVOID)GetProcAddress(dbghelpHandle, "MiniDumpWriteDump");
|
|
SymbolServerGetOptions = (PVOID)GetProcAddress(symsrvHandle, "SymbolServerGetOptions");
|
|
SymbolServerSetOptions = (PVOID)GetProcAddress(symsrvHandle, "SymbolServerSetOptions");
|
|
|
|
if (SymGetOptions_I && SymSetOptions_I)
|
|
SymSetOptions_I(SymGetOptions_I() | SYMOPT_DEFERRED_LOADS | SYMOPT_FAVOR_COMPRESSED);
|
|
}
|
|
|
|
PPH_SYMBOL_PROVIDER PhCreateSymbolProvider(
|
|
_In_opt_ HANDLE ProcessId
|
|
)
|
|
{
|
|
PPH_SYMBOL_PROVIDER symbolProvider;
|
|
|
|
symbolProvider = PhCreateObject(sizeof(PH_SYMBOL_PROVIDER), PhSymbolProviderType);
|
|
memset(symbolProvider, 0, sizeof(PH_SYMBOL_PROVIDER));
|
|
InitializeListHead(&symbolProvider->ModulesListHead);
|
|
PhInitializeQueuedLock(&symbolProvider->ModulesListLock);
|
|
PhInitializeAvlTree(&symbolProvider->ModulesSet, PhpSymbolModuleCompareFunction);
|
|
PhInitializeCallback(&symbolProvider->EventCallback);
|
|
PhInitializeInitOnce(&symbolProvider->InitOnce);
|
|
|
|
if (ProcessId)
|
|
{
|
|
static ACCESS_MASK accesses[] =
|
|
{
|
|
STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff, // pre-Vista full access
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE,
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
|
MAXIMUM_ALLOWED
|
|
};
|
|
|
|
ULONG i;
|
|
|
|
// Try to open the process with many different accesses.
|
|
// This handle will be re-used when walking stacks, and doing various other things.
|
|
for (i = 0; i < sizeof(accesses) / sizeof(ACCESS_MASK); i++)
|
|
{
|
|
if (NT_SUCCESS(PhOpenProcess(&symbolProvider->ProcessHandle, accesses[i], ProcessId)))
|
|
{
|
|
symbolProvider->IsRealHandle = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!symbolProvider->IsRealHandle)
|
|
{
|
|
HANDLE fakeHandle;
|
|
|
|
// Just generate a fake handle.
|
|
fakeHandle = (HANDLE)_InterlockedExchangeAddPointer((PLONG_PTR)&PhNextFakeHandle, 4);
|
|
// Add one to make sure it isn't divisible by 4 (so it can't be mistaken for a real handle).
|
|
symbolProvider->ProcessHandle = (HANDLE)((ULONG_PTR)fakeHandle + 1);
|
|
}
|
|
|
|
return symbolProvider;
|
|
}
|
|
|
|
VOID NTAPI PhpSymbolProviderDeleteProcedure(
|
|
_In_ PVOID Object,
|
|
_In_ ULONG Flags
|
|
)
|
|
{
|
|
PPH_SYMBOL_PROVIDER symbolProvider = (PPH_SYMBOL_PROVIDER)Object;
|
|
PLIST_ENTRY listEntry;
|
|
|
|
PhDeleteCallback(&symbolProvider->EventCallback);
|
|
|
|
if (SymCleanup_I)
|
|
{
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
if (symbolProvider->IsRegistered)
|
|
SymCleanup_I(symbolProvider->ProcessHandle);
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
}
|
|
|
|
listEntry = symbolProvider->ModulesListHead.Flink;
|
|
|
|
while (listEntry != &symbolProvider->ModulesListHead)
|
|
{
|
|
PPH_SYMBOL_MODULE module;
|
|
|
|
module = CONTAINING_RECORD(listEntry, PH_SYMBOL_MODULE, ListEntry);
|
|
listEntry = listEntry->Flink;
|
|
|
|
PhpFreeSymbolModule(module);
|
|
}
|
|
|
|
if (symbolProvider->IsRealHandle) NtClose(symbolProvider->ProcessHandle);
|
|
}
|
|
|
|
NTSTATUS PhpSymbolCallbackWorker(
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
PPH_SYMBOL_EVENT_DATA data = Parameter;
|
|
|
|
dprintf("symbol event %d: %S\n", data->Type, data->FileName->Buffer);
|
|
PhInvokeCallback(&data->SymbolProvider->EventCallback, data);
|
|
PhClearReference(&data->FileName);
|
|
PhDereferenceObject(data);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
BOOL CALLBACK PhpSymbolCallbackFunction(
|
|
_In_ HANDLE hProcess,
|
|
_In_ ULONG ActionCode,
|
|
_In_opt_ ULONG64 CallbackData,
|
|
_In_opt_ ULONG64 UserContext
|
|
)
|
|
{
|
|
PPH_SYMBOL_PROVIDER symbolProvider = (PPH_SYMBOL_PROVIDER)UserContext;
|
|
PPH_SYMBOL_EVENT_DATA data;
|
|
PIMAGEHLP_DEFERRED_SYMBOL_LOADW64 callbackData;
|
|
|
|
if (!IsListEmpty(&symbolProvider->EventCallback.ListHead))
|
|
{
|
|
switch (ActionCode)
|
|
{
|
|
case SymbolDeferredSymbolLoadStart:
|
|
case SymbolDeferredSymbolLoadComplete:
|
|
case SymbolDeferredSymbolLoadFailure:
|
|
case SymbolSymbolsUnloaded:
|
|
case SymbolDeferredSymbolLoadCancel:
|
|
data = PhCreateAlloc(sizeof(PH_SYMBOL_EVENT_DATA));
|
|
memset(data, 0, sizeof(PH_SYMBOL_EVENT_DATA));
|
|
data->SymbolProvider = symbolProvider;
|
|
data->Type = ActionCode;
|
|
|
|
if (ActionCode != SymbolSymbolsUnloaded)
|
|
{
|
|
callbackData = (PIMAGEHLP_DEFERRED_SYMBOL_LOADW64)CallbackData;
|
|
data->BaseAddress = callbackData->BaseOfImage;
|
|
data->CheckSum = callbackData->CheckSum;
|
|
data->TimeStamp = callbackData->TimeDateStamp;
|
|
data->FileName = PhCreateString(callbackData->FileName);
|
|
}
|
|
|
|
PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), PhpSymbolCallbackWorker, data);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID PhpRegisterSymbolProvider(
|
|
_In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider
|
|
)
|
|
{
|
|
if (PhBeginInitOnce(&PhSymInitOnce))
|
|
{
|
|
PhInvokeCallback(&PhSymInitCallback, NULL);
|
|
PhEndInitOnce(&PhSymInitOnce);
|
|
}
|
|
|
|
if (!SymbolProvider)
|
|
return;
|
|
|
|
if (PhBeginInitOnce(&SymbolProvider->InitOnce))
|
|
{
|
|
if (SymInitialize_I)
|
|
{
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
SymInitialize_I(SymbolProvider->ProcessHandle, NULL, FALSE);
|
|
|
|
if (SymRegisterCallbackW64_I)
|
|
SymRegisterCallbackW64_I(SymbolProvider->ProcessHandle, PhpSymbolCallbackFunction, (ULONG64)SymbolProvider);
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
|
|
SymbolProvider->IsRegistered = TRUE;
|
|
}
|
|
|
|
PhEndInitOnce(&SymbolProvider->InitOnce);
|
|
}
|
|
}
|
|
|
|
VOID PhpFreeSymbolModule(
|
|
_In_ PPH_SYMBOL_MODULE SymbolModule
|
|
)
|
|
{
|
|
if (SymbolModule->FileName) PhDereferenceObject(SymbolModule->FileName);
|
|
|
|
PhFree(SymbolModule);
|
|
}
|
|
|
|
static LONG NTAPI PhpSymbolModuleCompareFunction(
|
|
_In_ PPH_AVL_LINKS Links1,
|
|
_In_ PPH_AVL_LINKS Links2
|
|
)
|
|
{
|
|
PPH_SYMBOL_MODULE symbolModule1 = CONTAINING_RECORD(Links1, PH_SYMBOL_MODULE, Links);
|
|
PPH_SYMBOL_MODULE symbolModule2 = CONTAINING_RECORD(Links2, PH_SYMBOL_MODULE, Links);
|
|
|
|
return uint64cmp(symbolModule1->BaseAddress, symbolModule2->BaseAddress);
|
|
}
|
|
|
|
BOOLEAN PhGetLineFromAddress(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ ULONG64 Address,
|
|
_Out_ PPH_STRING *FileName,
|
|
_Out_opt_ PULONG Displacement,
|
|
_Out_opt_ PPH_SYMBOL_LINE_INFORMATION Information
|
|
)
|
|
{
|
|
IMAGEHLP_LINEW64 line;
|
|
BOOL result;
|
|
ULONG displacement;
|
|
PPH_STRING fileName;
|
|
|
|
PhpRegisterSymbolProvider(SymbolProvider);
|
|
|
|
if (!SymGetLineFromAddrW64_I && !SymGetLineFromAddr64_I)
|
|
return FALSE;
|
|
|
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64);
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
if (SymGetLineFromAddrW64_I)
|
|
{
|
|
result = SymGetLineFromAddrW64_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Address,
|
|
&displacement,
|
|
&line
|
|
);
|
|
|
|
if (result)
|
|
fileName = PhCreateString(line.FileName);
|
|
}
|
|
else
|
|
{
|
|
IMAGEHLP_LINE64 lineA;
|
|
|
|
lineA.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
|
|
|
result = SymGetLineFromAddr64_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Address,
|
|
&displacement,
|
|
&lineA
|
|
);
|
|
|
|
if (result)
|
|
{
|
|
fileName = PhConvertMultiByteToUtf16(lineA.FileName);
|
|
line.LineNumber = lineA.LineNumber;
|
|
line.Address = lineA.Address;
|
|
}
|
|
}
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
*FileName = fileName;
|
|
|
|
if (Displacement)
|
|
*Displacement = displacement;
|
|
|
|
if (Information)
|
|
{
|
|
Information->LineNumber = line.LineNumber;
|
|
Information->Address = line.Address;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
ULONG64 PhGetModuleFromAddress(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ ULONG64 Address,
|
|
_Out_opt_ PPH_STRING *FileName
|
|
)
|
|
{
|
|
PH_SYMBOL_MODULE lookupModule;
|
|
PPH_AVL_LINKS links;
|
|
PPH_SYMBOL_MODULE module;
|
|
PPH_STRING foundFileName;
|
|
ULONG64 foundBaseAddress;
|
|
|
|
foundFileName = NULL;
|
|
foundBaseAddress = 0;
|
|
|
|
PhAcquireQueuedLockShared(&SymbolProvider->ModulesListLock);
|
|
|
|
// Do an approximate search on the modules set to locate the module with the largest
|
|
// base address that is still smaller than the given address.
|
|
lookupModule.BaseAddress = Address;
|
|
links = PhUpperDualBoundElementAvlTree(&SymbolProvider->ModulesSet, &lookupModule.Links);
|
|
|
|
if (links)
|
|
{
|
|
module = CONTAINING_RECORD(links, PH_SYMBOL_MODULE, Links);
|
|
|
|
if (Address < module->BaseAddress + module->Size)
|
|
{
|
|
PhSetReference(&foundFileName, module->FileName);
|
|
foundBaseAddress = module->BaseAddress;
|
|
}
|
|
}
|
|
|
|
PhReleaseQueuedLockShared(&SymbolProvider->ModulesListLock);
|
|
|
|
if (foundFileName)
|
|
{
|
|
if (FileName)
|
|
{
|
|
*FileName = foundFileName;
|
|
}
|
|
else
|
|
{
|
|
PhDereferenceObject(foundFileName);
|
|
}
|
|
}
|
|
|
|
return foundBaseAddress;
|
|
}
|
|
|
|
VOID PhpSymbolInfoAnsiToUnicode(
|
|
_Out_ PSYMBOL_INFOW SymbolInfoW,
|
|
_In_ PSYMBOL_INFO SymbolInfoA
|
|
)
|
|
{
|
|
SymbolInfoW->TypeIndex = SymbolInfoA->TypeIndex;
|
|
SymbolInfoW->Index = SymbolInfoA->Index;
|
|
SymbolInfoW->Size = SymbolInfoA->Size;
|
|
SymbolInfoW->ModBase = SymbolInfoA->ModBase;
|
|
SymbolInfoW->Flags = SymbolInfoA->Flags;
|
|
SymbolInfoW->Value = SymbolInfoA->Value;
|
|
SymbolInfoW->Address = SymbolInfoA->Address;
|
|
SymbolInfoW->Register = SymbolInfoA->Register;
|
|
SymbolInfoW->Scope = SymbolInfoA->Scope;
|
|
SymbolInfoW->Tag = SymbolInfoA->Tag;
|
|
SymbolInfoW->NameLen = 0;
|
|
|
|
if (SymbolInfoA->NameLen != 0 && SymbolInfoW->MaxNameLen != 0)
|
|
{
|
|
ULONG copyCount;
|
|
|
|
copyCount = min(SymbolInfoA->NameLen, SymbolInfoW->MaxNameLen - 1);
|
|
|
|
if (PhCopyStringZFromMultiByte(
|
|
SymbolInfoA->Name,
|
|
copyCount,
|
|
SymbolInfoW->Name,
|
|
SymbolInfoW->MaxNameLen,
|
|
NULL
|
|
))
|
|
{
|
|
SymbolInfoW->NameLen = copyCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
PPH_STRING PhGetSymbolFromAddress(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ ULONG64 Address,
|
|
_Out_opt_ PPH_SYMBOL_RESOLVE_LEVEL ResolveLevel,
|
|
_Out_opt_ PPH_STRING *FileName,
|
|
_Out_opt_ PPH_STRING *SymbolName,
|
|
_Out_opt_ PULONG64 Displacement
|
|
)
|
|
{
|
|
PSYMBOL_INFOW symbolInfo;
|
|
ULONG nameLength;
|
|
PPH_STRING symbol = NULL;
|
|
PH_SYMBOL_RESOLVE_LEVEL resolveLevel;
|
|
ULONG64 displacement;
|
|
PPH_STRING modFileName = NULL;
|
|
PPH_STRING modBaseName = NULL;
|
|
ULONG64 modBase;
|
|
PPH_STRING symbolName = NULL;
|
|
|
|
if (Address == 0)
|
|
{
|
|
if (ResolveLevel) *ResolveLevel = PhsrlInvalid;
|
|
if (FileName) *FileName = NULL;
|
|
if (SymbolName) *SymbolName = NULL;
|
|
if (Displacement) *Displacement = 0;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PhpRegisterSymbolProvider(SymbolProvider);
|
|
|
|
if (!SymFromAddrW_I && !SymFromAddr_I)
|
|
return NULL;
|
|
|
|
symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + PH_MAX_SYMBOL_NAME_LEN * 2);
|
|
memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
|
|
symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
|
|
symbolInfo->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;
|
|
|
|
// Get the symbol name.
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
// Note that we don't care whether this call succeeds or not, based on the assumption that it
|
|
// will not write to the symbolInfo structure if it fails. We've already zeroed the structure,
|
|
// so we can deal with it.
|
|
|
|
if (SymFromAddrW_I)
|
|
{
|
|
SymFromAddrW_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Address,
|
|
&displacement,
|
|
symbolInfo
|
|
);
|
|
nameLength = symbolInfo->NameLen;
|
|
|
|
if (nameLength + 1 > PH_MAX_SYMBOL_NAME_LEN)
|
|
{
|
|
PhFree(symbolInfo);
|
|
symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + nameLength * 2 + 2);
|
|
memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
|
|
symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
|
|
symbolInfo->MaxNameLen = nameLength + 1;
|
|
|
|
SymFromAddrW_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Address,
|
|
&displacement,
|
|
symbolInfo
|
|
);
|
|
}
|
|
}
|
|
else if (SymFromAddr_I)
|
|
{
|
|
PSYMBOL_INFO symbolInfoA;
|
|
|
|
symbolInfoA = PhAllocate(FIELD_OFFSET(SYMBOL_INFO, Name) + PH_MAX_SYMBOL_NAME_LEN);
|
|
memset(symbolInfoA, 0, sizeof(SYMBOL_INFO));
|
|
symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
symbolInfoA->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;
|
|
|
|
SymFromAddr_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Address,
|
|
&displacement,
|
|
symbolInfoA
|
|
);
|
|
nameLength = symbolInfoA->NameLen;
|
|
|
|
if (nameLength + 1 > PH_MAX_SYMBOL_NAME_LEN)
|
|
{
|
|
PhFree(symbolInfoA);
|
|
symbolInfoA = PhAllocate(FIELD_OFFSET(SYMBOL_INFO, Name) + nameLength + 1);
|
|
memset(symbolInfoA, 0, sizeof(SYMBOL_INFO));
|
|
symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
symbolInfoA->MaxNameLen = nameLength + 1;
|
|
|
|
SymFromAddr_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Address,
|
|
&displacement,
|
|
symbolInfoA
|
|
);
|
|
|
|
// Also reallocate the Unicode-based buffer.
|
|
PhFree(symbolInfo);
|
|
symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + nameLength * 2 + 2);
|
|
memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
|
|
symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
|
|
symbolInfo->MaxNameLen = nameLength + 1;
|
|
}
|
|
|
|
PhpSymbolInfoAnsiToUnicode(symbolInfo, symbolInfoA);
|
|
PhFree(symbolInfoA);
|
|
}
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
|
|
// Find the module name.
|
|
|
|
if (symbolInfo->ModBase == 0)
|
|
{
|
|
modBase = PhGetModuleFromAddress(
|
|
SymbolProvider,
|
|
Address,
|
|
&modFileName
|
|
);
|
|
}
|
|
else
|
|
{
|
|
PH_SYMBOL_MODULE lookupSymbolModule;
|
|
PPH_AVL_LINKS existingLinks;
|
|
PPH_SYMBOL_MODULE symbolModule;
|
|
|
|
lookupSymbolModule.BaseAddress = symbolInfo->ModBase;
|
|
|
|
PhAcquireQueuedLockShared(&SymbolProvider->ModulesListLock);
|
|
|
|
existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links);
|
|
|
|
if (existingLinks)
|
|
{
|
|
symbolModule = CONTAINING_RECORD(existingLinks, PH_SYMBOL_MODULE, Links);
|
|
PhSetReference(&modFileName, symbolModule->FileName);
|
|
}
|
|
|
|
PhReleaseQueuedLockShared(&SymbolProvider->ModulesListLock);
|
|
}
|
|
|
|
// If we don't have a module name, return an address.
|
|
if (!modFileName)
|
|
{
|
|
resolveLevel = PhsrlAddress;
|
|
symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2);
|
|
PhPrintPointer(symbol->Buffer, (PVOID)Address);
|
|
PhTrimToNullTerminatorString(symbol);
|
|
|
|
goto CleanupExit;
|
|
}
|
|
|
|
modBaseName = PhGetBaseName(modFileName);
|
|
|
|
// If we have a module name but not a symbol name, return the module plus an offset:
|
|
// module+offset.
|
|
|
|
if (symbolInfo->NameLen == 0)
|
|
{
|
|
PH_FORMAT format[3];
|
|
|
|
resolveLevel = PhsrlModule;
|
|
|
|
PhInitFormatSR(&format[0], modBaseName->sr);
|
|
PhInitFormatS(&format[1], L"+0x");
|
|
PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase));
|
|
symbol = PhFormat(format, 3, modBaseName->Length + 6 + 32);
|
|
|
|
goto CleanupExit;
|
|
}
|
|
|
|
// If we have everything, return the full symbol name: module!symbol+offset.
|
|
|
|
symbolName = PhCreateStringEx(symbolInfo->Name, symbolInfo->NameLen * 2);
|
|
resolveLevel = PhsrlFunction;
|
|
|
|
if (displacement == 0)
|
|
{
|
|
PH_FORMAT format[3];
|
|
|
|
PhInitFormatSR(&format[0], modBaseName->sr);
|
|
PhInitFormatC(&format[1], '!');
|
|
PhInitFormatSR(&format[2], symbolName->sr);
|
|
|
|
symbol = PhFormat(format, 3, modBaseName->Length + 2 + symbolName->Length);
|
|
}
|
|
else
|
|
{
|
|
PH_FORMAT format[5];
|
|
|
|
PhInitFormatSR(&format[0], modBaseName->sr);
|
|
PhInitFormatC(&format[1], '!');
|
|
PhInitFormatSR(&format[2], symbolName->sr);
|
|
PhInitFormatS(&format[3], L"+0x");
|
|
PhInitFormatIX(&format[4], (ULONG_PTR)displacement);
|
|
|
|
symbol = PhFormat(format, 5, modBaseName->Length + 2 + symbolName->Length + 6 + 32);
|
|
}
|
|
|
|
CleanupExit:
|
|
|
|
if (ResolveLevel)
|
|
*ResolveLevel = resolveLevel;
|
|
if (FileName)
|
|
PhSetReference(FileName, modFileName);
|
|
if (SymbolName)
|
|
PhSetReference(SymbolName, symbolName);
|
|
if (Displacement)
|
|
*Displacement = displacement;
|
|
|
|
PhClearReference(&modFileName);
|
|
PhClearReference(&modBaseName);
|
|
PhClearReference(&symbolName);
|
|
PhFree(symbolInfo);
|
|
|
|
return symbol;
|
|
}
|
|
|
|
BOOLEAN PhGetSymbolFromName(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ PWSTR Name,
|
|
_Out_ PPH_SYMBOL_INFORMATION Information
|
|
)
|
|
{
|
|
PSYMBOL_INFOW symbolInfo;
|
|
UCHAR symbolInfoBuffer[FIELD_OFFSET(SYMBOL_INFOW, Name) + PH_MAX_SYMBOL_NAME_LEN * 2];
|
|
BOOL result;
|
|
|
|
PhpRegisterSymbolProvider(SymbolProvider);
|
|
|
|
if (!SymFromNameW_I && !SymFromName_I)
|
|
return FALSE;
|
|
|
|
symbolInfo = (PSYMBOL_INFOW)symbolInfoBuffer;
|
|
memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
|
|
symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
|
|
symbolInfo->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;
|
|
|
|
// Get the symbol information.
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
if (SymFromNameW_I)
|
|
{
|
|
result = SymFromNameW_I(
|
|
SymbolProvider->ProcessHandle,
|
|
Name,
|
|
symbolInfo
|
|
);
|
|
}
|
|
else if (SymFromName_I)
|
|
{
|
|
UCHAR buffer[FIELD_OFFSET(SYMBOL_INFO, Name) + PH_MAX_SYMBOL_NAME_LEN];
|
|
PSYMBOL_INFO symbolInfoA;
|
|
PPH_BYTES name;
|
|
|
|
symbolInfoA = (PSYMBOL_INFO)buffer;
|
|
memset(symbolInfoA, 0, sizeof(SYMBOL_INFO));
|
|
symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
symbolInfoA->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;
|
|
|
|
name = PhConvertUtf16ToMultiByte(Name);
|
|
|
|
if (result = SymFromName_I(
|
|
SymbolProvider->ProcessHandle,
|
|
name->Buffer,
|
|
symbolInfoA
|
|
))
|
|
{
|
|
PhpSymbolInfoAnsiToUnicode(symbolInfo, symbolInfoA);
|
|
}
|
|
|
|
PhDereferenceObject(name);
|
|
}
|
|
else
|
|
{
|
|
result = FALSE;
|
|
}
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
Information->Address = symbolInfo->Address;
|
|
Information->ModuleBase = symbolInfo->ModBase;
|
|
Information->Index = symbolInfo->Index;
|
|
Information->Size = symbolInfo->Size;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN PhLoadModuleSymbolProvider(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ PWSTR FileName,
|
|
_In_ ULONG64 BaseAddress,
|
|
_In_ ULONG Size
|
|
)
|
|
{
|
|
ULONG64 baseAddress;
|
|
PPH_SYMBOL_MODULE symbolModule = NULL;
|
|
PPH_AVL_LINKS existingLinks;
|
|
PH_SYMBOL_MODULE lookupSymbolModule;
|
|
|
|
PhpRegisterSymbolProvider(SymbolProvider);
|
|
|
|
if (!SymLoadModuleExW_I && !SymLoadModule64_I)
|
|
return FALSE;
|
|
|
|
// Check for duplicates. It is better to do this before calling SymLoadModuleExW, because it
|
|
// seems to force symbol loading when it is called twice on the same module even if deferred
|
|
// loading is enabled.
|
|
PhAcquireQueuedLockExclusive(&SymbolProvider->ModulesListLock);
|
|
lookupSymbolModule.BaseAddress = BaseAddress;
|
|
existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links);
|
|
PhReleaseQueuedLockExclusive(&SymbolProvider->ModulesListLock);
|
|
|
|
if (existingLinks)
|
|
return TRUE;
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
if (SymLoadModuleExW_I)
|
|
{
|
|
baseAddress = SymLoadModuleExW_I(
|
|
SymbolProvider->ProcessHandle,
|
|
NULL,
|
|
FileName,
|
|
NULL,
|
|
BaseAddress,
|
|
Size,
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
else
|
|
{
|
|
PPH_BYTES fileName;
|
|
|
|
fileName = PhConvertUtf16ToMultiByte(FileName);
|
|
baseAddress = SymLoadModule64_I(
|
|
SymbolProvider->ProcessHandle,
|
|
NULL,
|
|
fileName->Buffer,
|
|
NULL,
|
|
BaseAddress,
|
|
Size
|
|
);
|
|
PhDereferenceObject(fileName);
|
|
}
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
|
|
// Add the module to the list, even if we couldn't load symbols for the module.
|
|
|
|
PhAcquireQueuedLockExclusive(&SymbolProvider->ModulesListLock);
|
|
|
|
// Check for duplicates again.
|
|
lookupSymbolModule.BaseAddress = BaseAddress;
|
|
existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links);
|
|
|
|
if (!existingLinks)
|
|
{
|
|
symbolModule = PhAllocate(sizeof(PH_SYMBOL_MODULE));
|
|
symbolModule->BaseAddress = BaseAddress;
|
|
symbolModule->Size = Size;
|
|
symbolModule->FileName = PhGetFullPath(FileName, &symbolModule->BaseNameIndex);
|
|
|
|
existingLinks = PhAddElementAvlTree(&SymbolProvider->ModulesSet, &symbolModule->Links);
|
|
assert(!existingLinks);
|
|
InsertTailList(&SymbolProvider->ModulesListHead, &symbolModule->ListEntry);
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&SymbolProvider->ModulesListLock);
|
|
|
|
if (!baseAddress)
|
|
{
|
|
if (GetLastError() != ERROR_SUCCESS)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID PhSetOptionsSymbolProvider(
|
|
_In_ ULONG Mask,
|
|
_In_ ULONG Value
|
|
)
|
|
{
|
|
ULONG options;
|
|
|
|
PhpRegisterSymbolProvider(NULL);
|
|
|
|
if (!SymGetOptions_I || !SymSetOptions_I)
|
|
return;
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
options = SymGetOptions_I();
|
|
options &= ~Mask;
|
|
options |= Value;
|
|
SymSetOptions_I(options);
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
}
|
|
|
|
VOID PhSetSearchPathSymbolProvider(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ PWSTR Path
|
|
)
|
|
{
|
|
PhpRegisterSymbolProvider(SymbolProvider);
|
|
|
|
if (!SymSetSearchPathW_I && !SymSetSearchPath_I)
|
|
return;
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
|
|
if (SymSetSearchPathW_I)
|
|
{
|
|
SymSetSearchPathW_I(SymbolProvider->ProcessHandle, Path);
|
|
}
|
|
else if (SymSetSearchPath_I)
|
|
{
|
|
PPH_BYTES path;
|
|
|
|
path = PhConvertUtf16ToMultiByte(Path);
|
|
SymSetSearchPath_I(SymbolProvider->ProcessHandle, path->Buffer);
|
|
PhDereferenceObject(path);
|
|
}
|
|
|
|
PH_UNLOCK_SYMBOLS();
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
|
|
NTSTATUS PhpLookupDynamicFunctionTable(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ ULONG64 Address,
|
|
_Out_opt_ PDYNAMIC_FUNCTION_TABLE *FunctionTableAddress,
|
|
_Out_opt_ PDYNAMIC_FUNCTION_TABLE FunctionTable,
|
|
_Out_writes_bytes_opt_(OutOfProcessCallbackDllBufferSize) PWCHAR OutOfProcessCallbackDllBuffer,
|
|
_In_ ULONG OutOfProcessCallbackDllBufferSize,
|
|
_Out_opt_ PUNICODE_STRING OutOfProcessCallbackDllString
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PLIST_ENTRY (NTAPI *rtlGetFunctionTableListHead)(VOID);
|
|
PLIST_ENTRY tableListHead;
|
|
LIST_ENTRY tableListHeadEntry;
|
|
PLIST_ENTRY tableListEntry;
|
|
PDYNAMIC_FUNCTION_TABLE functionTableAddress;
|
|
DYNAMIC_FUNCTION_TABLE functionTable;
|
|
ULONG count;
|
|
SIZE_T numberOfBytesRead;
|
|
ULONG i;
|
|
BOOLEAN foundNull;
|
|
|
|
rtlGetFunctionTableListHead = PhGetModuleProcAddress(L"ntdll.dll", "RtlGetFunctionTableListHead");
|
|
|
|
if (!rtlGetFunctionTableListHead)
|
|
return STATUS_PROCEDURE_NOT_FOUND;
|
|
|
|
tableListHead = rtlGetFunctionTableListHead();
|
|
|
|
// Find the function table entry for this address.
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
tableListHead,
|
|
&tableListHeadEntry,
|
|
sizeof(LIST_ENTRY),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
tableListEntry = tableListHeadEntry.Flink;
|
|
count = 0; // make sure we can't be forced into an infinite loop by crafted data
|
|
|
|
while (tableListEntry != tableListHead && count < PH_ENUM_PROCESS_MODULES_LIMIT)
|
|
{
|
|
functionTableAddress = CONTAINING_RECORD(tableListEntry, DYNAMIC_FUNCTION_TABLE, ListEntry);
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
functionTableAddress,
|
|
&functionTable,
|
|
sizeof(DYNAMIC_FUNCTION_TABLE),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
if (Address >= functionTable.MinimumAddress && Address < functionTable.MaximumAddress)
|
|
{
|
|
if (OutOfProcessCallbackDllBuffer)
|
|
{
|
|
if (functionTable.OutOfProcessCallbackDll)
|
|
{
|
|
// Read the out-of-process callback DLL path. We don't have a length, so we'll
|
|
// just have to read as much as possible.
|
|
|
|
memset(OutOfProcessCallbackDllBuffer, 0xff, OutOfProcessCallbackDllBufferSize);
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
functionTable.OutOfProcessCallbackDll,
|
|
OutOfProcessCallbackDllBuffer,
|
|
OutOfProcessCallbackDllBufferSize,
|
|
&numberOfBytesRead
|
|
);
|
|
|
|
if (status != STATUS_PARTIAL_COPY && !NT_SUCCESS(status))
|
|
return status;
|
|
|
|
foundNull = FALSE;
|
|
|
|
for (i = 0; i < OutOfProcessCallbackDllBufferSize / sizeof(WCHAR); i++)
|
|
{
|
|
if (OutOfProcessCallbackDllBuffer[i] == 0)
|
|
{
|
|
foundNull = TRUE;
|
|
|
|
if (OutOfProcessCallbackDllString)
|
|
{
|
|
OutOfProcessCallbackDllString->Buffer = OutOfProcessCallbackDllBuffer;
|
|
OutOfProcessCallbackDllString->Length = (USHORT)(i * sizeof(WCHAR));
|
|
OutOfProcessCallbackDllString->MaximumLength = OutOfProcessCallbackDllString->Length;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there was no null terminator, then we didn't read the whole string in.
|
|
// Fail the operation.
|
|
if (!foundNull)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
else
|
|
{
|
|
OutOfProcessCallbackDllBuffer[0] = 0;
|
|
|
|
if (OutOfProcessCallbackDllString)
|
|
{
|
|
OutOfProcessCallbackDllString->Buffer = NULL;
|
|
OutOfProcessCallbackDllString->Length = 0;
|
|
OutOfProcessCallbackDllString->MaximumLength = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FunctionTableAddress)
|
|
*FunctionTableAddress = functionTableAddress;
|
|
|
|
if (FunctionTable)
|
|
*FunctionTable = functionTable;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
tableListEntry = functionTable.ListEntry.Flink;
|
|
count++;
|
|
}
|
|
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
PRUNTIME_FUNCTION PhpLookupFunctionEntry(
|
|
_In_ PRUNTIME_FUNCTION Functions,
|
|
_In_ ULONG NumberOfFunctions,
|
|
_In_ BOOLEAN Sorted,
|
|
_In_ ULONG64 RelativeControlPc
|
|
)
|
|
{
|
|
LONG low;
|
|
LONG high;
|
|
ULONG i;
|
|
|
|
if (Sorted)
|
|
{
|
|
if (NumberOfFunctions == 0)
|
|
return NULL;
|
|
|
|
low = 0;
|
|
high = NumberOfFunctions - 1;
|
|
|
|
do
|
|
{
|
|
i = (low + high) / 2;
|
|
|
|
if (RelativeControlPc < Functions[i].BeginAddress)
|
|
high = i - 1;
|
|
else if (RelativeControlPc >= Functions[i].EndAddress)
|
|
low = i + 1;
|
|
else
|
|
return &Functions[i];
|
|
} while (low <= high);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < NumberOfFunctions; i++)
|
|
{
|
|
if (RelativeControlPc >= Functions[i].BeginAddress && RelativeControlPc < Functions[i].EndAddress)
|
|
return &Functions[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
NTSTATUS PhpAccessCallbackFunctionTable(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PVOID FunctionTableAddress,
|
|
_In_ PUNICODE_STRING OutOfProcessCallbackDllString,
|
|
_Out_ PRUNTIME_FUNCTION *Functions,
|
|
_Out_ PULONG NumberOfFunctions
|
|
)
|
|
{
|
|
static PH_STRINGREF knownFunctionTableDllsKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\KnownFunctionTableDlls");
|
|
|
|
NTSTATUS status;
|
|
HANDLE keyHandle;
|
|
ULONG returnLength;
|
|
PVOID dllHandle;
|
|
ANSI_STRING outOfProcessFunctionTableCallbackName;
|
|
POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK outOfProcessFunctionTableCallback;
|
|
|
|
if (!OutOfProcessCallbackDllString->Buffer)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
// Don't load DLLs from arbitrary locations. Check if this is a known function table DLL.
|
|
|
|
if (!NT_SUCCESS(status = PhOpenKey(
|
|
&keyHandle,
|
|
KEY_READ,
|
|
PH_KEY_LOCAL_MACHINE,
|
|
&knownFunctionTableDllsKeyName,
|
|
0
|
|
)))
|
|
return status;
|
|
|
|
status = NtQueryValueKey(keyHandle, OutOfProcessCallbackDllString, KeyValuePartialInformation, NULL, 0, &returnLength);
|
|
NtClose(keyHandle);
|
|
|
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
return STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT;
|
|
|
|
status = LdrLoadDll(NULL, NULL, OutOfProcessCallbackDllString, &dllHandle);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
RtlInitAnsiString(&outOfProcessFunctionTableCallbackName, OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK_EXPORT_NAME);
|
|
status = LdrGetProcedureAddress(dllHandle, &outOfProcessFunctionTableCallbackName, 0, (PVOID *)&outOfProcessFunctionTableCallback);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
status = outOfProcessFunctionTableCallback(
|
|
ProcessHandle,
|
|
FunctionTableAddress,
|
|
NumberOfFunctions,
|
|
Functions
|
|
);
|
|
}
|
|
|
|
LdrUnloadDll(dllHandle);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpAccessNormalFunctionTable(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PDYNAMIC_FUNCTION_TABLE FunctionTable,
|
|
_Out_ PRUNTIME_FUNCTION *Functions,
|
|
_Out_ PULONG NumberOfFunctions
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
SIZE_T bufferSize;
|
|
PRUNTIME_FUNCTION functions;
|
|
|
|
// Put a reasonable limit on the number of entries we read.
|
|
if (FunctionTable->EntryCount > 0x100000)
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
bufferSize = FunctionTable->EntryCount * sizeof(RUNTIME_FUNCTION);
|
|
functions = PhAllocatePage(bufferSize, NULL);
|
|
|
|
if (!functions)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
status = NtReadVirtualMemory(ProcessHandle, FunctionTable->FunctionTable, functions, bufferSize, NULL);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Functions = functions;
|
|
*NumberOfFunctions = FunctionTable->EntryCount;
|
|
}
|
|
else
|
|
{
|
|
PhFreePage(functions);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhAccessOutOfProcessFunctionEntry(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ ULONG64 ControlPc,
|
|
_Out_ PRUNTIME_FUNCTION Function
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PDYNAMIC_FUNCTION_TABLE functionTableAddress;
|
|
DYNAMIC_FUNCTION_TABLE functionTable;
|
|
WCHAR outOfProcessCallbackDll[512];
|
|
UNICODE_STRING outOfProcessCallbackDllString;
|
|
PRUNTIME_FUNCTION functions;
|
|
ULONG numberOfFunctions;
|
|
PRUNTIME_FUNCTION function;
|
|
|
|
if (!NT_SUCCESS(status = PhpLookupDynamicFunctionTable(
|
|
ProcessHandle,
|
|
ControlPc,
|
|
&functionTableAddress,
|
|
&functionTable,
|
|
outOfProcessCallbackDll,
|
|
sizeof(outOfProcessCallbackDll),
|
|
&outOfProcessCallbackDllString
|
|
)))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
if (functionTable.Type == RF_CALLBACK)
|
|
{
|
|
if (!NT_SUCCESS(status = PhpAccessCallbackFunctionTable(
|
|
ProcessHandle,
|
|
functionTableAddress,
|
|
&outOfProcessCallbackDllString,
|
|
&functions,
|
|
&numberOfFunctions
|
|
)))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
function = PhpLookupFunctionEntry(functions, numberOfFunctions, FALSE, ControlPc - functionTable.BaseAddress);
|
|
|
|
if (function)
|
|
*Function = *function;
|
|
else
|
|
status = STATUS_NOT_FOUND;
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, functions);
|
|
}
|
|
else
|
|
{
|
|
if (!NT_SUCCESS(status = PhpAccessNormalFunctionTable(
|
|
ProcessHandle,
|
|
&functionTable,
|
|
&functions,
|
|
&numberOfFunctions
|
|
)))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
function = PhpLookupFunctionEntry(functions, numberOfFunctions, functionTable.Type == RF_SORTED, ControlPc - functionTable.BaseAddress);
|
|
|
|
if (function)
|
|
*Function = *function;
|
|
else
|
|
status = STATUS_NOT_FOUND;
|
|
|
|
PhFreePage(functions);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
#endif
|
|
|
|
ULONG64 __stdcall PhGetModuleBase64(
|
|
_In_ HANDLE hProcess,
|
|
_In_ DWORD64 dwAddr
|
|
)
|
|
{
|
|
ULONG64 base;
|
|
#ifdef _WIN64
|
|
DYNAMIC_FUNCTION_TABLE functionTable;
|
|
#endif
|
|
|
|
#ifdef _WIN64
|
|
if (NT_SUCCESS(PhpLookupDynamicFunctionTable(
|
|
hProcess,
|
|
dwAddr,
|
|
NULL,
|
|
&functionTable,
|
|
NULL,
|
|
0,
|
|
NULL
|
|
)))
|
|
{
|
|
base = functionTable.BaseAddress;
|
|
}
|
|
else
|
|
{
|
|
base = 0;
|
|
}
|
|
#else
|
|
base = 0;
|
|
#endif
|
|
|
|
if (base == 0 && SymGetModuleBase64_I)
|
|
base = SymGetModuleBase64_I(hProcess, dwAddr);
|
|
|
|
return base;
|
|
}
|
|
|
|
PVOID __stdcall PhFunctionTableAccess64(
|
|
_In_ HANDLE hProcess,
|
|
_In_ DWORD64 AddrBase
|
|
)
|
|
{
|
|
#ifdef _WIN64
|
|
static RUNTIME_FUNCTION lastRuntimeFunction;
|
|
#endif
|
|
|
|
PVOID entry;
|
|
|
|
#ifdef _WIN64
|
|
if (NT_SUCCESS(PhAccessOutOfProcessFunctionEntry(hProcess, AddrBase, &lastRuntimeFunction)))
|
|
entry = &lastRuntimeFunction;
|
|
else
|
|
entry = NULL;
|
|
#else
|
|
entry = NULL;
|
|
#endif
|
|
|
|
if (!entry && SymFunctionTableAccess64_I)
|
|
entry = SymFunctionTableAccess64_I(hProcess, AddrBase);
|
|
|
|
return entry;
|
|
}
|
|
|
|
BOOLEAN PhStackWalk(
|
|
_In_ ULONG MachineType,
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE ThreadHandle,
|
|
_Inout_ LPSTACKFRAME64 StackFrame,
|
|
_Inout_ PVOID ContextRecord,
|
|
_In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
|
|
_In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
|
|
_In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
|
|
_In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
|
|
)
|
|
{
|
|
BOOLEAN result;
|
|
|
|
PhpRegisterSymbolProvider(SymbolProvider);
|
|
|
|
if (!StackWalk64_I)
|
|
return FALSE;
|
|
|
|
if (!FunctionTableAccessRoutine)
|
|
{
|
|
if (MachineType == IMAGE_FILE_MACHINE_AMD64)
|
|
FunctionTableAccessRoutine = PhFunctionTableAccess64;
|
|
else
|
|
FunctionTableAccessRoutine = SymFunctionTableAccess64_I;
|
|
}
|
|
|
|
if (!GetModuleBaseRoutine)
|
|
{
|
|
if (MachineType == IMAGE_FILE_MACHINE_AMD64)
|
|
GetModuleBaseRoutine = PhGetModuleBase64;
|
|
else
|
|
GetModuleBaseRoutine = SymGetModuleBase64_I;
|
|
}
|
|
|
|
PH_LOCK_SYMBOLS();
|
|
result = StackWalk64_I(
|
|
MachineType,
|
|
ProcessHandle,
|
|
ThreadHandle,
|
|
StackFrame,
|
|
ContextRecord,
|
|
ReadMemoryRoutine,
|
|
FunctionTableAccessRoutine,
|
|
GetModuleBaseRoutine,
|
|
TranslateAddress
|
|
);
|
|
PH_UNLOCK_SYMBOLS();
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOLEAN PhWriteMiniDumpProcess(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE ProcessId,
|
|
_In_ HANDLE FileHandle,
|
|
_In_ MINIDUMP_TYPE DumpType,
|
|
_In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
|
|
_In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
|
_In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam
|
|
)
|
|
{
|
|
PhpRegisterSymbolProvider(NULL);
|
|
|
|
if (!MiniDumpWriteDump_I)
|
|
{
|
|
SetLastError(ERROR_PROC_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
return MiniDumpWriteDump_I(
|
|
ProcessHandle,
|
|
HandleToUlong(ProcessId),
|
|
FileHandle,
|
|
DumpType,
|
|
ExceptionParam,
|
|
UserStreamParam,
|
|
CallbackParam
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Converts a STACKFRAME64 structure to a PH_THREAD_STACK_FRAME structure.
|
|
*
|
|
* \param StackFrame64 A pointer to the STACKFRAME64 structure to convert.
|
|
* \param Flags Flags to set in the resulting structure.
|
|
* \param ThreadStackFrame A pointer to the resulting PH_THREAD_STACK_FRAME structure.
|
|
*/
|
|
VOID PhpConvertStackFrame(
|
|
_In_ STACKFRAME64 *StackFrame64,
|
|
_In_ ULONG Flags,
|
|
_Out_ PPH_THREAD_STACK_FRAME ThreadStackFrame
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
ThreadStackFrame->PcAddress = (PVOID)StackFrame64->AddrPC.Offset;
|
|
ThreadStackFrame->ReturnAddress = (PVOID)StackFrame64->AddrReturn.Offset;
|
|
ThreadStackFrame->FrameAddress = (PVOID)StackFrame64->AddrFrame.Offset;
|
|
ThreadStackFrame->StackAddress = (PVOID)StackFrame64->AddrStack.Offset;
|
|
ThreadStackFrame->BStoreAddress = (PVOID)StackFrame64->AddrBStore.Offset;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
ThreadStackFrame->Params[i] = (PVOID)StackFrame64->Params[i];
|
|
|
|
ThreadStackFrame->Flags = Flags;
|
|
|
|
if (StackFrame64->FuncTableEntry)
|
|
ThreadStackFrame->Flags |= PH_THREAD_STACK_FRAME_FPO_DATA_PRESENT;
|
|
}
|
|
|
|
/**
|
|
* Walks a thread's stack.
|
|
*
|
|
* \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION,
|
|
* THREAD_GET_CONTEXT and THREAD_SUSPEND_RESUME access. The handle can have any access for kernel
|
|
* stack walking.
|
|
* \param ProcessHandle A handle to the thread's parent process. The handle must have
|
|
* PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access. If a symbol provider is being used, pass
|
|
* its process handle and specify the symbol provider in \a SymbolProvider.
|
|
* \param ClientId The client ID identifying the thread.
|
|
* \param SymbolProvider The associated symbol provider.
|
|
* \param Flags A combination of flags.
|
|
* \li \c PH_WALK_I386_STACK Walks the x86 stack. On AMD64 systems this flag walks the WOW64 stack.
|
|
* \li \c PH_WALK_AMD64_STACK Walks the AMD64 stack. On x86 systems this flag is ignored.
|
|
* \li \c PH_WALK_KERNEL_STACK Walks the kernel stack. This flag is ignored if there is no active
|
|
* KProcessHacker connection.
|
|
* \param Callback A callback function which is executed for each stack frame.
|
|
* \param Context A user-defined value to pass to the callback function.
|
|
*/
|
|
NTSTATUS PhWalkThreadStack(
|
|
_In_ HANDLE ThreadHandle,
|
|
_In_opt_ HANDLE ProcessHandle,
|
|
_In_opt_ PCLIENT_ID ClientId,
|
|
_In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ ULONG Flags,
|
|
_In_ PPH_WALK_THREAD_STACK_CALLBACK Callback,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
BOOLEAN suspended = FALSE;
|
|
BOOLEAN processOpened = FALSE;
|
|
BOOLEAN isCurrentThread = FALSE;
|
|
BOOLEAN isSystemThread = FALSE;
|
|
THREAD_BASIC_INFORMATION basicInfo;
|
|
|
|
// Open a handle to the process if we weren't given one.
|
|
if (!ProcessHandle)
|
|
{
|
|
if (KphIsConnected() || !ClientId)
|
|
{
|
|
if (!NT_SUCCESS(status = PhOpenThreadProcess(
|
|
ThreadHandle,
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
|
&ProcessHandle
|
|
)))
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
if (!NT_SUCCESS(status = PhOpenProcess(
|
|
&ProcessHandle,
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
|
ClientId->UniqueProcess
|
|
)))
|
|
return status;
|
|
}
|
|
|
|
processOpened = TRUE;
|
|
}
|
|
|
|
// Determine if the caller specified the current thread.
|
|
if (ClientId)
|
|
{
|
|
if (ClientId->UniqueThread == NtCurrentTeb()->ClientId.UniqueThread)
|
|
isCurrentThread = TRUE;
|
|
if (ClientId->UniqueProcess == SYSTEM_PROCESS_ID)
|
|
isSystemThread = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (ThreadHandle == NtCurrentThread())
|
|
{
|
|
isCurrentThread = TRUE;
|
|
}
|
|
else if (NT_SUCCESS(PhGetThreadBasicInformation(ThreadHandle, &basicInfo)))
|
|
{
|
|
if (basicInfo.ClientId.UniqueThread == NtCurrentTeb()->ClientId.UniqueThread)
|
|
isCurrentThread = TRUE;
|
|
if (basicInfo.ClientId.UniqueProcess == SYSTEM_PROCESS_ID)
|
|
isSystemThread = TRUE;
|
|
}
|
|
}
|
|
|
|
// Make sure this isn't a kernel-mode thread.
|
|
if (!isSystemThread)
|
|
{
|
|
PVOID startAddress;
|
|
|
|
if (NT_SUCCESS(NtQueryInformationThread(ThreadHandle, ThreadQuerySetWin32StartAddress,
|
|
&startAddress, sizeof(PVOID), NULL)))
|
|
{
|
|
if ((ULONG_PTR)startAddress > PhSystemBasicInformation.MaximumUserModeAddress)
|
|
isSystemThread = TRUE;
|
|
}
|
|
}
|
|
|
|
// Suspend the thread to avoid inaccurate results. Don't suspend if we're walking the stack of
|
|
// the current thread or this is a kernel-mode thread.
|
|
if (!isCurrentThread && !isSystemThread)
|
|
{
|
|
if (NT_SUCCESS(NtSuspendThread(ThreadHandle, NULL)))
|
|
suspended = TRUE;
|
|
}
|
|
|
|
// Kernel stack walk.
|
|
if ((Flags & PH_WALK_KERNEL_STACK) && KphIsConnected())
|
|
{
|
|
PVOID stack[256 - 2]; // See MAX_STACK_DEPTH
|
|
ULONG capturedFrames;
|
|
ULONG i;
|
|
|
|
if (NT_SUCCESS(KphCaptureStackBackTraceThread(
|
|
ThreadHandle,
|
|
1,
|
|
sizeof(stack) / sizeof(PVOID),
|
|
stack,
|
|
&capturedFrames,
|
|
NULL
|
|
)))
|
|
{
|
|
PH_THREAD_STACK_FRAME threadStackFrame;
|
|
|
|
memset(&threadStackFrame, 0, sizeof(PH_THREAD_STACK_FRAME));
|
|
|
|
for (i = 0; i < capturedFrames; i++)
|
|
{
|
|
threadStackFrame.PcAddress = stack[i];
|
|
threadStackFrame.Flags = PH_THREAD_STACK_FRAME_KERNEL;
|
|
|
|
if (!Callback(&threadStackFrame, Context))
|
|
{
|
|
goto ResumeExit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
if (Flags & PH_WALK_AMD64_STACK)
|
|
{
|
|
STACKFRAME64 stackFrame;
|
|
PH_THREAD_STACK_FRAME threadStackFrame;
|
|
CONTEXT context;
|
|
|
|
context.ContextFlags = CONTEXT_ALL;
|
|
|
|
if (!NT_SUCCESS(status = NtGetContextThread(
|
|
ThreadHandle,
|
|
&context
|
|
)))
|
|
goto SkipAmd64Stack;
|
|
|
|
memset(&stackFrame, 0, sizeof(STACKFRAME64));
|
|
stackFrame.AddrPC.Mode = AddrModeFlat;
|
|
stackFrame.AddrPC.Offset = context.Rip;
|
|
stackFrame.AddrStack.Mode = AddrModeFlat;
|
|
stackFrame.AddrStack.Offset = context.Rsp;
|
|
stackFrame.AddrFrame.Mode = AddrModeFlat;
|
|
stackFrame.AddrFrame.Offset = context.Rbp;
|
|
|
|
while (TRUE)
|
|
{
|
|
if (!PhStackWalk(
|
|
IMAGE_FILE_MACHINE_AMD64,
|
|
ProcessHandle,
|
|
ThreadHandle,
|
|
&stackFrame,
|
|
&context,
|
|
SymbolProvider,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
))
|
|
break;
|
|
|
|
// If we have an invalid instruction pointer, break.
|
|
if (!stackFrame.AddrPC.Offset || stackFrame.AddrPC.Offset == -1)
|
|
break;
|
|
|
|
// Convert the stack frame and execute the callback.
|
|
|
|
PhpConvertStackFrame(&stackFrame, PH_THREAD_STACK_FRAME_AMD64, &threadStackFrame);
|
|
|
|
if (!Callback(&threadStackFrame, Context))
|
|
goto ResumeExit;
|
|
}
|
|
}
|
|
|
|
SkipAmd64Stack:
|
|
#endif
|
|
|
|
// x86/WOW64 stack walk.
|
|
if (Flags & PH_WALK_I386_STACK)
|
|
{
|
|
STACKFRAME64 stackFrame;
|
|
PH_THREAD_STACK_FRAME threadStackFrame;
|
|
#ifndef _WIN64
|
|
CONTEXT context;
|
|
|
|
context.ContextFlags = CONTEXT_ALL;
|
|
|
|
if (!NT_SUCCESS(status = NtGetContextThread(
|
|
ThreadHandle,
|
|
&context
|
|
)))
|
|
goto SkipI386Stack;
|
|
#else
|
|
WOW64_CONTEXT context;
|
|
|
|
context.ContextFlags = WOW64_CONTEXT_ALL;
|
|
|
|
if (!NT_SUCCESS(status = NtQueryInformationThread(
|
|
ThreadHandle,
|
|
ThreadWow64Context,
|
|
&context,
|
|
sizeof(WOW64_CONTEXT),
|
|
NULL
|
|
)))
|
|
goto SkipI386Stack;
|
|
#endif
|
|
|
|
memset(&stackFrame, 0, sizeof(STACKFRAME64));
|
|
stackFrame.AddrPC.Mode = AddrModeFlat;
|
|
stackFrame.AddrPC.Offset = context.Eip;
|
|
stackFrame.AddrStack.Mode = AddrModeFlat;
|
|
stackFrame.AddrStack.Offset = context.Esp;
|
|
stackFrame.AddrFrame.Mode = AddrModeFlat;
|
|
stackFrame.AddrFrame.Offset = context.Ebp;
|
|
|
|
while (TRUE)
|
|
{
|
|
if (!PhStackWalk(
|
|
IMAGE_FILE_MACHINE_I386,
|
|
ProcessHandle,
|
|
ThreadHandle,
|
|
&stackFrame,
|
|
&context,
|
|
SymbolProvider,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
))
|
|
break;
|
|
|
|
// If we have an invalid instruction pointer, break.
|
|
if (!stackFrame.AddrPC.Offset || stackFrame.AddrPC.Offset == -1)
|
|
break;
|
|
|
|
// Convert the stack frame and execute the callback.
|
|
|
|
PhpConvertStackFrame(&stackFrame, PH_THREAD_STACK_FRAME_I386, &threadStackFrame);
|
|
|
|
if (!Callback(&threadStackFrame, Context))
|
|
goto ResumeExit;
|
|
|
|
// (x86 only) Allow the user to change Eip, Esp and Ebp.
|
|
context.Eip = PtrToUlong(threadStackFrame.PcAddress);
|
|
stackFrame.AddrPC.Offset = PtrToUlong(threadStackFrame.PcAddress);
|
|
context.Ebp = PtrToUlong(threadStackFrame.FrameAddress);
|
|
stackFrame.AddrFrame.Offset = PtrToUlong(threadStackFrame.FrameAddress);
|
|
context.Esp = PtrToUlong(threadStackFrame.StackAddress);
|
|
stackFrame.AddrStack.Offset = PtrToUlong(threadStackFrame.StackAddress);
|
|
}
|
|
}
|
|
|
|
SkipI386Stack:
|
|
|
|
ResumeExit:
|
|
if (suspended)
|
|
NtResumeThread(ThreadHandle, NULL);
|
|
|
|
if (processOpened)
|
|
NtClose(ProcessHandle);
|
|
|
|
return status;
|
|
}
|