/** * @file symbol.cpp * @author Alee Amini (alee@hyperdbg.org) * @author Sina Karvandi (sina@hyperdbg.org) * @brief symbol parser * @details * @version 0.1 * @date 2021-05-20 * * @copyright This project is released under the GNU Public License v3. * */ #include "pch.h" // // Global Variables // extern PMODULE_SYMBOL_DETAIL g_SymbolTable; extern UINT32 g_SymbolTableSize; extern UINT32 g_SymbolTableCurrentIndex; extern BOOLEAN g_IsExecutingSymbolLoadingRoutines; extern BOOLEAN g_AddressConversion; extern std::map g_DisassemblerSymbolMap; using namespace std; /** * @brief Initial load of symbols (for previously download symbols) * * @return VOID */ VOID SymbolInitialReload() { // // Load already downloaded symbol (won't download at this point) // ShowMessages("interpreting symbols and creating symbol maps\n"); SymbolLoadOrDownloadSymbols(FALSE, TRUE); } /** * @brief Locally reload the symbol table * @param UserProcessId * * @return BOOLEAN */ BOOLEAN SymbolLocalReload(UINT32 UserProcessId) { ShowMessages("interpreting symbols and creating symbol maps\n"); SymbolBuildSymbolTable(&g_SymbolTable, &g_SymbolTableSize, UserProcessId, FALSE); // // And also load the symbols // return SymbolLoadOrDownloadSymbols(FALSE, TRUE); } /** * @brief Initial and send the results of serial for the debugger * in the case of debugger mode * @param UserProcessId * * @return VOID */ VOID SymbolPrepareDebuggerWithSymbolInfo(UINT32 UserProcessId) { // // Load already downloaded symbol (won't download at this point) // SymbolBuildSymbolTable(&g_SymbolTable, &g_SymbolTableSize, UserProcessId, TRUE); } /** * @brief Callback for creating symbol map for disassembler * * @param Address * @param ModuleName * @param ObjectName * @param ObjectSize * * @return VOID */ VOID SymbolCreateDisassemblerMapCallback(UINT64 Address, CHAR * ModuleName, CHAR * ObjectName, UINT32 ObjectSize) { // // It has a string, should not be initialized with zero // LOCAL_FUNCTION_DESCRIPTION LocalFunctionDescription = {}; string FinalModuleName = ""; if (ObjectSize == 0) { ObjectSize = DISASSEMBLY_MAXIMUM_DISTANCE_FROM_OBJECT_NAME; } // // Convert module name to string // if (ModuleName != NULL) { FinalModuleName += std::string(ModuleName) + "!"; } // // Convert object name to string // if (ObjectName != NULL) { FinalModuleName += std::string(ObjectName); } // // Create the structure // LocalFunctionDescription.ObjectName = std::move(FinalModuleName); LocalFunctionDescription.ObjectSize = ObjectSize; // // Add to disassembler map // g_DisassemblerSymbolMap[Address] = LocalFunctionDescription; } /** * @brief Update (or create) symbol map for the disassembler * * @return BOOLEAN */ BOOLEAN SymbolCreateDisassemblerSymbolMap() { // // Clear the map table // g_DisassemblerSymbolMap.clear(); // // Get all the symbols in the callback // ScriptEngineCreateSymbolTableForDisassemblerWrapper(SymbolCreateDisassemblerMapCallback); return TRUE; } /** * @brief shows the functions' name for the disassembler * @param Address * @param UsedBaseAddress * * @return BOOLEAN */ BOOLEAN SymbolShowFunctionNameBasedOnAddress(UINT64 Address, PUINT64 UsedBaseAddress) { std::map::iterator Low, Prev; UINT64 Pos = Address; // // Check if showing function (object) names is not prohibited // form settings command // if (!g_AddressConversion) { return FALSE; } // // Check if we already built the symbol map for disassembler or not // if (!g_DisassemblerSymbolMap.empty()) { Low = g_DisassemblerSymbolMap.lower_bound(Pos); if (Low == g_DisassemblerSymbolMap.end()) { // // Nothing found, maybe use rbegin() // return FALSE; } else if (Low == g_DisassemblerSymbolMap.begin() && Low->first > Address) { // // Nothing to do, address is below the lowest entry in symbol table // return FALSE; } else if (Low->first == Address) { if (*UsedBaseAddress != Address) { ShowMessages("%s", Low->second.ObjectName.c_str()); *UsedBaseAddress = Address; return TRUE; } return FALSE; } else { Prev = std::prev(Low); UINT64 Diff = Address - Prev->first; // // Check, so we have a threshold boundary to add +xx to the // symbols function name, in otherwords, the maximum number of // bytes that a function could contain (it's definitely not the // best option to find start and end of function, it's an approximate // and not always might be true) // if (Prev->second.ObjectSize >= Diff) { if (*UsedBaseAddress != Prev->first) { ShowMessages("%s+0x%x", Prev->second.ObjectName.c_str(), Diff); *UsedBaseAddress = Prev->first; return TRUE; } return FALSE; } else if (DISASSEMBLY_MAXIMUM_DISTANCE_FROM_OBJECT_NAME >= Diff) { // // We add the logic of adding Name+X+X to show that a address is x bytes // after the Object Name and not within the size of the function but x // bytes from the above of the function // if (*UsedBaseAddress != Prev->first) { ShowMessages("%s+0x%x+0x%x", Prev->second.ObjectName.c_str(), Diff, Diff - Prev->second.ObjectSize); *UsedBaseAddress = Prev->first; return TRUE; } return FALSE; } } } // // Nothing is showed // return FALSE; } /** * @brief Build and show symbol table details * @param BuildLocalSymTable Should this function call to build local symbol * or the symbols are from a remote debuggee in debugger mode * * @return VOID */ VOID SymbolBuildAndShowSymbolTable() { if (g_SymbolTable == NULL || g_SymbolTableSize == NULL) { ShowMessages("err, symbol table is empty. please use '.sym reload' " "to build the symbol table\n"); return; } // // show packet details // for (SIZE_T i = 0; i < g_SymbolTableSize / sizeof(MODULE_SYMBOL_DETAIL); i++) { ShowMessages("is pdb details available? : %s\n", g_SymbolTable[i].IsSymbolDetailsFound ? "true" : "false"); ShowMessages("is pdb a path instead of module name? : %s\n", g_SymbolTable[i].IsLocalSymbolPath ? "true" : "false"); ShowMessages("base address : %llx\n", g_SymbolTable[i].BaseAddress); ShowMessages("file path : %s\n", g_SymbolTable[i].FilePath); ShowMessages("guid and age : %s\n", g_SymbolTable[i].ModuleSymbolGuidAndAge); ShowMessages("module symbol path/name : %s\n", g_SymbolTable[i].ModuleSymbolPath); ShowMessages("is user-mode? : %s - is 32-bit? %s\n", g_SymbolTable[i].IsUserMode ? "true" : "false", g_SymbolTable[i].Is32Bit ? "true" : "false"); ShowMessages("========================================================================\n"); } } /** * @brief Load or download symbols * @param IsDownload Download from remote server if not available locally * @param SilentLoad Load without any message * * @return BOOLEAN */ BOOLEAN SymbolLoadOrDownloadSymbols(BOOLEAN IsDownload, BOOLEAN SilentLoad) { string SymbolServer; BOOLEAN Result = FALSE; // // *** Read symbol path/server from config file *** // if (!CommandSettingsGetValueFromConfigFile("SymbolServer", SymbolServer)) { ShowMessages("please configure the symbol path (use '.help .sympath' for more information)\n"); return FALSE; } // // Check if symbol table is empty // if (g_SymbolTable == NULL || g_SymbolTableSize == NULL) { ShowMessages("symbol table is empty, please use '.sym reload' to build a symbol table\n"); return FALSE; } // // *** Load or download available symbols *** // // // Indicate that we're in loading routines // g_IsExecutingSymbolLoadingRoutines = TRUE; Result = ScriptEngineSymbolInitLoadWrapper(g_SymbolTable, g_SymbolTableSize, IsDownload, SymbolServer.c_str(), SilentLoad); // // Build symbol table for disassembler // SymbolCreateDisassemblerSymbolMap(); // // Not in loading routines anymore // g_IsExecutingSymbolLoadingRoutines = FALSE; return Result; } /** * @brief check and convert string to a 64 bit unsigned integer and also * check for symbol object names and evaluate expressions * * @param TextToConvert the target string * @param Result result will be save to the pointer * * @return BOOLEAN shows whether the conversion was successful or not */ BOOLEAN SymbolConvertNameOrExprToAddress(const string & TextToConvert, PUINT64 Result) { BOOLEAN IsFound = FALSE; BOOLEAN HasError = NULL; UINT64 Address = NULL; if (!ConvertStringToUInt64(TextToConvert, &Address)) { // // Check for symbol object names // string ConstTextToConvert = TextToConvert; Address = ScriptEngineConvertNameToAddressWrapper(ConstTextToConvert.c_str(), &IsFound); if (!IsFound) { // // It's neither a number, nor a founded object name, // as the last resort, we have to test whether it's an expression or not // if we're in the Debugger Mode then we have to send it the kernel to get // the evaluation, if we're in VMI mode, then we evaluate it here with all // registers set to Zero // Address = ScriptEngineEvalSingleExpression(TextToConvert, &HasError); if (HasError) { // // Not found or has error // IsFound = FALSE; } else { // // Expression evaluated successfully // IsFound = TRUE; } } else { // // Object name is found // IsFound = TRUE; } } else { // // It's a hex number // IsFound = TRUE; } // // Set the number if the address is founded // if (IsFound) { *Result = Address; } return IsFound; } /** * @brief Read user virtual memory of the debuggee process * * @param BaseAddress the base address to read from * @param UserProcessId the target process id to read its memory * @param ReadSize the size of bytes to read * @param Bytes the output vector to receive the read bytes * * @return BOOLEAN shows whether the reading was successful or not */ static BOOLEAN SymbolReadUserVirtualMemoryExact(UINT64 BaseAddress, UINT32 UserProcessId, UINT32 ReadSize, std::vector & Bytes) { BOOL Status = FALSE; ULONG ReturnedLength = 0; if (ReadSize > 0xffffffffu - SIZEOF_DEBUGGER_READ_MEMORY) { return FALSE; } UINT32 SizeOfTargetBuffer = SIZEOF_DEBUGGER_READ_MEMORY + ReadSize; DEBUGGER_READ_MEMORY ReadMemoryRequest = {0}; DEBUGGER_READ_MEMORY * MemReadRequest = NULL; Bytes.clear(); if (g_DeviceHandle == NULL || BaseAddress == 0 || UserProcessId == 0 || ReadSize == 0) { return FALSE; } MemReadRequest = (DEBUGGER_READ_MEMORY *)malloc(SizeOfTargetBuffer); if (MemReadRequest == NULL) { return FALSE; } ReadMemoryRequest.Address = BaseAddress; ReadMemoryRequest.Pid = UserProcessId; ReadMemoryRequest.Size = ReadSize; ReadMemoryRequest.MemoryType = DEBUGGER_READ_VIRTUAL_ADDRESS; ReadMemoryRequest.ReadingType = READ_FROM_KERNEL; RtlZeroMemory(MemReadRequest, SizeOfTargetBuffer); memcpy(MemReadRequest, &ReadMemoryRequest, sizeof(DEBUGGER_READ_MEMORY)); Status = DeviceIoControl(g_DeviceHandle, IOCTL_DEBUGGER_READ_MEMORY, MemReadRequest, SIZEOF_DEBUGGER_READ_MEMORY, MemReadRequest, SizeOfTargetBuffer, &ReturnedLength, NULL); if (!Status || MemReadRequest->KernelStatus != DEBUGGER_OPERATION_WAS_SUCCESSFUL || ReturnedLength != SizeOfTargetBuffer) { free(MemReadRequest); return FALSE; } Bytes.resize(ReadSize); memcpy(Bytes.data(), ((UCHAR *)MemReadRequest) + SIZEOF_DEBUGGER_READ_MEMORY, ReadSize); free(MemReadRequest); return TRUE; } /** * @brief Check if the provided range is valid and update the required size to read if needed * * @param Offset the offset of the range to check * @param Length the length of the range to check * @param RequiredSize a pointer to the variable containing the current required size, which will be updated if the end of the range exceeds it * * @return BOOLEAN TRUE if the range is valid and RequiredSize is updated if needed, FALSE if the range is invalid (e.g., due to overflow) */ static BOOLEAN SymbolAddLoadedImageReadRange(UINT32 Offset, UINT32 Length, UINT32 * RequiredSize) { UINT32 EndOffset = 0; if (RequiredSize == NULL) { return FALSE; } if (Length == 0) { return TRUE; } if (Offset > 0xffffffffu - Length) { return FALSE; } EndOffset = Offset + Length; if (EndOffset > *RequiredSize) { *RequiredSize = EndOffset; } return TRUE; } /** * @brief Check if the provided range is within the bounds of the loaded image bytes * * @param LoadedImageBytes the vector containing the bytes of the loaded image * @param Offset the offset of the range to check * @param Length the length of the range to check * * @return BOOLEAN TRUE if the range is within bounds, FALSE otherwise */ static BOOLEAN SymbolLoadedImageHasRange(const std::vector & LoadedImageBytes, UINT32 Offset, UINT32 Length) { return Length <= LoadedImageBytes.size() && Offset <= LoadedImageBytes.size() - Length; } /** * @brief Parse the headers of a loaded PE image from the provided bytes and extract the SizeOfImage and DebugDirectory details * * @param LoadedImagePrefix a vector containing the initial bytes of the loaded image, which should include at least the DOS header and NT headers * @param SizeOfImage an output pointer to receive the SizeOfImage extracted from the optional header * @param DebugDirectory an optional output pointer to receive the IMAGE_DATA_DIRECTORY entry for the debug directory if available (can be NULL if not needed) * * @return BOOLEAN TRUE if the headers were successfully parsed and SizeOfImage was extracted, FALSE otherwise (e.g., if the headers are invalid or incomplete) */ static BOOLEAN SymbolGetLoadedImageHeaderDetails(const std::vector & LoadedImagePrefix, UINT32 * SizeOfImage, IMAGE_DATA_DIRECTORY * DebugDirectory) { if (LoadedImagePrefix.size() < sizeof(IMAGE_DOS_HEADER) || SizeOfImage == NULL) { return FALSE; } if (DebugDirectory != NULL) { RtlZeroMemory(DebugDirectory, sizeof(*DebugDirectory)); } const IMAGE_DOS_HEADER * DosHeader = (const IMAGE_DOS_HEADER *)LoadedImagePrefix.data(); if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE || DosHeader->e_lfanew < 0) { return FALSE; } SIZE_T NtHeaderOffset = (SIZE_T)DosHeader->e_lfanew; if (NtHeaderOffset > LoadedImagePrefix.size() || sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) > LoadedImagePrefix.size() - NtHeaderOffset) { return FALSE; } const BYTE * NtHeaders = LoadedImagePrefix.data() + NtHeaderOffset; if (*(const DWORD *)NtHeaders != IMAGE_NT_SIGNATURE) { return FALSE; } const IMAGE_FILE_HEADER * FileHeader = (const IMAGE_FILE_HEADER *)(NtHeaders + sizeof(DWORD)); SIZE_T OptionalHeaderOffset = NtHeaderOffset + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER); if (OptionalHeaderOffset > LoadedImagePrefix.size() || FileHeader->SizeOfOptionalHeader < sizeof(WORD) || FileHeader->SizeOfOptionalHeader > LoadedImagePrefix.size() - OptionalHeaderOffset) { return FALSE; } const BYTE * OptionalHeader = LoadedImagePrefix.data() + OptionalHeaderOffset; WORD Magic = *(const WORD *)OptionalHeader; if (Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC && FileHeader->SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER32)) { const IMAGE_OPTIONAL_HEADER32 * OptionalHeader32 = (const IMAGE_OPTIONAL_HEADER32 *)OptionalHeader; *SizeOfImage = OptionalHeader32->SizeOfImage; if (DebugDirectory != NULL && OptionalHeader32->NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_DEBUG) { *DebugDirectory = OptionalHeader32->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; } return TRUE; } if (Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC && FileHeader->SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER64)) { const IMAGE_OPTIONAL_HEADER64 * OptionalHeader64 = (const IMAGE_OPTIONAL_HEADER64 *)OptionalHeader; *SizeOfImage = OptionalHeader64->SizeOfImage; if (DebugDirectory != NULL && OptionalHeader64->NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_DEBUG) { *DebugDirectory = OptionalHeader64->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; } return TRUE; } return FALSE; } /** * @brief Read the necessary bytes of a loaded user-mode module to extract the debug directory and code view information for symbol loading * * @param BaseAddress the base address of the loaded module in the user process * @param UserProcessId the target process id to read its memory * @param LoadedImageBytes an output vector to receive the bytes of the loaded image that are necessary for symbol extraction (should be cleared and filled by this function) * * @return BOOLEAN TRUE if the necessary bytes were successfully read, FALSE otherwise (e.g., if memory reading failed or headers are invalid) */ static BOOLEAN SymbolReadLoadedUserModuleForCodeView(UINT64 BaseAddress, UINT32 UserProcessId, std::vector & LoadedImageBytes) { static constexpr UINT32 InitialLoadedImagePrefixSize = 4 * 1024; static constexpr UINT32 MaximumLoadedImageCodeViewReadSize = 4 * 1024 * 1024; UINT32 SizeOfImage = 0; UINT32 RequiredSize = InitialLoadedImagePrefixSize; IMAGE_DATA_DIRECTORY DebugDirectory = {0}; if (!SymbolReadUserVirtualMemoryExact(BaseAddress, UserProcessId, InitialLoadedImagePrefixSize, LoadedImageBytes)) { return FALSE; } if (!SymbolGetLoadedImageHeaderDetails(LoadedImageBytes, &SizeOfImage, &DebugDirectory) || SizeOfImage == 0) { return TRUE; } if (DebugDirectory.VirtualAddress == 0 || DebugDirectory.Size < sizeof(IMAGE_DEBUG_DIRECTORY) || DebugDirectory.Size % sizeof(IMAGE_DEBUG_DIRECTORY) != 0) { return TRUE; } if (!SymbolAddLoadedImageReadRange(DebugDirectory.VirtualAddress, DebugDirectory.Size, &RequiredSize) || RequiredSize > SizeOfImage || RequiredSize > MaximumLoadedImageCodeViewReadSize) { return TRUE; } if (RequiredSize > LoadedImageBytes.size() && !SymbolReadUserVirtualMemoryExact(BaseAddress, UserProcessId, RequiredSize, LoadedImageBytes)) { return FALSE; } if (SymbolLoadedImageHasRange(LoadedImageBytes, DebugDirectory.VirtualAddress, DebugDirectory.Size)) { const IMAGE_DEBUG_DIRECTORY * DebugEntries = (const IMAGE_DEBUG_DIRECTORY *)(LoadedImageBytes.data() + DebugDirectory.VirtualAddress); DWORD DebugEntryCount = DebugDirectory.Size / sizeof(IMAGE_DEBUG_DIRECTORY); for (DWORD Index = 0; Index < DebugEntryCount; Index++) { const IMAGE_DEBUG_DIRECTORY * DebugEntry = &DebugEntries[Index]; if (DebugEntry->Type != IMAGE_DEBUG_TYPE_CODEVIEW || DebugEntry->SizeOfData < sizeof(DWORD) + sizeof(GUID) + sizeof(DWORD)) { continue; } UINT32 NextRequiredSize = RequiredSize; if (!SymbolAddLoadedImageReadRange(DebugEntry->AddressOfRawData, DebugEntry->SizeOfData, &NextRequiredSize) || NextRequiredSize > SizeOfImage || NextRequiredSize > MaximumLoadedImageCodeViewReadSize) { continue; } RequiredSize = NextRequiredSize; } } if (RequiredSize <= LoadedImageBytes.size()) { LoadedImageBytes.resize(RequiredSize); return TRUE; } return SymbolReadUserVirtualMemoryExact(BaseAddress, UserProcessId, RequiredSize, LoadedImageBytes); } /** * @brief Delete and free structures and variables related to the symbols * * @return BOOLEAN shows whether the operation was successful or not */ BOOLEAN SymbolDeleteSymTable() { // // Unload all symbols // ScriptEngineUnloadAllSymbolsWrapper(); // // Delete symbols // if (g_SymbolTable != NULL) { free(g_SymbolTable); g_SymbolTable = NULL; g_SymbolTableSize = NULL; g_SymbolTableCurrentIndex = 0; return TRUE; } else { return FALSE; } } /** * @brief make the initial packet required for symbol server * or reload packet * * @param BufferToStoreDetails Pointer to a buffer to store the symbols details * this buffer will be allocated by this function and needs to be freed by caller * @param StoredLength The length that stored on the BufferToStoreDetails * @param UserProcessId Which user mode process to get its modules * @param SendOverSerial Shows whether the packet should be sent to the debugger * over the serial or not * * @return BOOLEAN shows whether the operation was successful or not */ BOOLEAN SymbolBuildSymbolTable(PMODULE_SYMBOL_DETAIL * BufferToStoreDetails, PUINT32 StoredLength, UINT32 UserProcessId, BOOLEAN SendOverSerial) { BOOLEAN Status; ULONG ReturnedLength; PRTL_PROCESS_MODULES ModuleInfo = NULL; PMODULE_SYMBOL_DETAIL ModuleSymDetailArray = NULL; CHAR SystemRoot[MAX_PATH] = {0}; CHAR ModuleSymbolPath[MAX_PATH] = {0}; CHAR TempPath[MAX_PATH] = {0}; CHAR ModuleSymbolGuidAndAge[MAXIMUM_GUID_AND_AGE_SIZE] = {0}; BOOLEAN IsSymbolPdbDetailAvailable = FALSE; BOOLEAN IsFreeUsermodeModulesBuffer = FALSE; UINT32 ModuleDetailsSize = 0; UINT32 ModulesCount = 0; PUSERMODE_LOADED_MODULE_DETAILS ModuleDetailsRequest = NULL; PUSERMODE_LOADED_MODULE_SYMBOLS Modules = NULL; USERMODE_LOADED_MODULE_DETAILS ModuleCountRequest = {0}; // // Check if we found an already built symbol table // SymbolDeleteSymTable(); // // Get system root // if (GetSystemDirectoryA(SystemRoot, MAX_PATH) == NULL) { ShowMessages("err, unable to get system directory (%x)\n", GetLastError()); return FALSE; } string SystemRootString(SystemRoot); // // Convert root path to lower-case // transform(SystemRootString.begin(), SystemRootString.end(), SystemRootString.begin(), [](UCHAR c) { return std::tolower(c); }); // // Remove system32 from the root // Replace(SystemRootString, "\\system32", ""); // // ***************************************************************** // Get kernel-mode modules information // ***************************************************************** // if (SymbolCheckAndAllocateModuleInformation(&ModuleInfo) == FALSE) { ShowMessages("err, unable to get module information\n"); return FALSE; } // // ***************************************************************** // Get user-mode modules information // ***************************************************************** // // // We won't fail the entire process if the user-mode modules is failed // do { // // Check if debugger is loaded or not // if (!g_DeviceHandle) { break; } // // Set the module details to get the details // ModuleCountRequest.ProcessId = UserProcessId; ModuleCountRequest.OnlyCountModules = TRUE; // // Send the request to the kernel // Status = DeviceIoControl( g_DeviceHandle, // Handle to device IOCTL_GET_USER_MODE_MODULE_DETAILS, // IO Control // code &ModuleCountRequest, // Input Buffer to driver. sizeof(USERMODE_LOADED_MODULE_DETAILS), // Input buffer length &ModuleCountRequest, // Output Buffer from driver. sizeof(USERMODE_LOADED_MODULE_DETAILS), // Length of output // buffer in bytes. &ReturnedLength, // Bytes placed in buffer. NULL // synchronous call ); if (!Status) { break; } // // Check if counting modules was successful or not // if (ModuleCountRequest.Result == DEBUGGER_OPERATION_WAS_SUCCESSFUL) { ModulesCount = ModuleCountRequest.ModulesCount; // ShowMessages("Count of modules : 0x%x\n", ModuleCountRequest.ModulesCount); ModuleDetailsSize = sizeof(USERMODE_LOADED_MODULE_DETAILS) + (ModuleCountRequest.ModulesCount * sizeof(USERMODE_LOADED_MODULE_SYMBOLS)); ModuleDetailsRequest = (PUSERMODE_LOADED_MODULE_DETAILS)malloc(ModuleDetailsSize); if (ModuleDetailsRequest == NULL) { break; } RtlZeroMemory(ModuleDetailsRequest, ModuleDetailsSize); // // Set the module details to get the modules (not count) // ModuleDetailsRequest->ProcessId = UserProcessId; ModuleDetailsRequest->OnlyCountModules = FALSE; // // Send the request to the kernel // Status = DeviceIoControl( g_DeviceHandle, // Handle to device IOCTL_GET_USER_MODE_MODULE_DETAILS, // IO Control // code ModuleDetailsRequest, // Input Buffer to driver. sizeof(USERMODE_LOADED_MODULE_DETAILS), // Input buffer length ModuleDetailsRequest, // Output Buffer from driver. ModuleDetailsSize, // Length of output // buffer in bytes. &ReturnedLength, // Bytes placed in buffer. NULL // synchronous call ); if (!Status) { free(ModuleDetailsRequest); break; } // // check the module list // if (ModuleDetailsRequest->Result == DEBUGGER_OPERATION_WAS_SUCCESSFUL) { // // Se the modules buffer // Modules = (PUSERMODE_LOADED_MODULE_SYMBOLS)((UINT64)ModuleDetailsRequest + sizeof(USERMODE_LOADED_MODULE_DETAILS)); // // Check if modules buffer is valid // if (Modules == NULL) { ModulesCount = 0; free(ModuleDetailsRequest); break; } } else { ShowErrorMessage(ModuleDetailsRequest->Result); free(ModuleDetailsRequest); break; } // // Everything is okay when we reached here // IsFreeUsermodeModulesBuffer = TRUE; } else { ShowErrorMessage(ModuleCountRequest.Result); break; } } while (FALSE); // // ***************************************************************** // // // Allocate Details buffer // ModuleSymDetailArray = (PMODULE_SYMBOL_DETAIL)malloc( (ModuleInfo->NumberOfModules + ModulesCount) * sizeof(MODULE_SYMBOL_DETAIL)); if (ModuleSymDetailArray == NULL) { ShowMessages("err, unable to allocate memory for module list (%x)\n", GetLastError()); if (IsFreeUsermodeModulesBuffer) { free(ModuleDetailsRequest); } free(ModuleInfo); return FALSE; } // // Make sure buffer is zero // RtlZeroMemory(ModuleSymDetailArray, (ModuleInfo->NumberOfModules + ModulesCount) * sizeof(MODULE_SYMBOL_DETAIL)); // // ---------------------------------------------------------------------------------- // Add user-modules // ---------------------------------------------------------------------------------- // if (ModulesCount != 0) { for (UINT32 i = 0; i < ModulesCount; i++) { // // For logging purpose // /* ShowMessages("%016llx\t%016llx\t%ws\n", Modules[i].BaseAddress, Modules[i].Entrypoint, Modules[i].FilePath); */ // // Read symbol signature details // RtlZeroMemory(ModuleSymbolPath, sizeof(ModuleSymbolPath)); RtlZeroMemory(TempPath, sizeof(TempPath)); RtlZeroMemory(ModuleSymbolGuidAndAge, sizeof(ModuleSymbolGuidAndAge)); std::vector LoadedImageBytes; // // Convert symbol path from unicode to ascii // wcstombs(TempPath, Modules[i].FilePath, MAX_PATH); if (SendOverSerial && Modules[i].BaseAddress != 0 && SymbolReadLoadedUserModuleForCodeView(Modules[i].BaseAddress, UserProcessId, LoadedImageBytes) && ScriptEngineConvertLoadedModuleToPdbFileAndGuidAndAgeDetailsWrapper(LoadedImageBytes.data(), LoadedImageBytes.size(), TempPath, ModuleSymbolPath, ModuleSymbolGuidAndAge, ModuleDetailsRequest->Is32Bit)) { IsSymbolPdbDetailAvailable = TRUE; // ShowMessages("Hash : %s , Symbol path : %s\n", ModuleSymbolGuidAndAge, ModuleSymbolPath); } else if (ScriptEngineConvertFileToPdbFileAndGuidAndAgeDetailsWrapper( TempPath, ModuleSymbolPath, ModuleSymbolGuidAndAge, ModuleDetailsRequest->Is32Bit)) { IsSymbolPdbDetailAvailable = TRUE; // ShowMessages("Hash : %s , Symbol path : %s\n", ModuleSymbolGuidAndAge, ModuleSymbolPath); } else { IsSymbolPdbDetailAvailable = FALSE; // ShowMessages("err, unable to get module pdb details\n"); } // // Build the structure for this module // ModuleSymDetailArray[i].BaseAddress = Modules[i].BaseAddress; ModuleSymDetailArray[i].IsUserMode = TRUE; ModuleSymDetailArray[i].Is32Bit = ModuleDetailsRequest->Is32Bit; memcpy(ModuleSymDetailArray[i].FilePath, TempPath, strlen(TempPath)); if (IsSymbolPdbDetailAvailable) { ModuleSymDetailArray[i].IsSymbolDetailsFound = TRUE; memcpy(ModuleSymDetailArray[i].ModuleSymbolGuidAndAge, ModuleSymbolGuidAndAge, MAXIMUM_GUID_AND_AGE_SIZE); memcpy(ModuleSymDetailArray[i].ModuleSymbolPath, ModuleSymbolPath, MAX_PATH); // // Check if pdb file name is a real path or a module name // string ModuleSymbolPathString(ModuleSymbolPath); if (ModuleSymbolPathString.find(":\\") != std::string::npos) ModuleSymDetailArray[i].IsLocalSymbolPath = TRUE; else ModuleSymDetailArray[i].IsLocalSymbolPath = FALSE; } else { ModuleSymDetailArray[i].IsSymbolDetailsFound = FALSE; } // // Check if it should be send to the remote debugger over serial // and also make sure that we're connected to the remote debugger // and this is a debuggee // if (SendOverSerial) { KdSendSymbolDetailPacket(&ModuleSymDetailArray[i], i, ModuleInfo->NumberOfModules + ModulesCount); } } } // // ---------------------------------------------------------------------------------- // Add kernel-modules // ---------------------------------------------------------------------------------- // for (UINT32 i = 0; i < ModuleInfo->NumberOfModules; i++) { UINT32 IndexInSymbolBuffer = ModulesCount + i; auto PathName = ModuleInfo->Modules[i].FullPathName + ModuleInfo->Modules[i].OffsetToFileName; // // Read symbol signature details // RtlZeroMemory(ModuleSymbolPath, sizeof(ModuleSymbolPath)); RtlZeroMemory(ModuleSymbolGuidAndAge, sizeof(ModuleSymbolGuidAndAge)); string ModuleFullPath((const CHAR *)ModuleInfo->Modules[i].FullPathName); if (ModuleFullPath.rfind("\\SystemRoot\\", 0) == 0) { // // Path starts with \SystemRoot\ // we should change it to the real system root // path // Replace(ModuleFullPath, "\\SystemRoot", SystemRootString); } // // Kernel modules are all 64-bit // if (ScriptEngineConvertFileToPdbFileAndGuidAndAgeDetailsWrapper(ModuleFullPath.c_str(), ModuleSymbolPath, ModuleSymbolGuidAndAge, FALSE)) { IsSymbolPdbDetailAvailable = TRUE; // // ShowMessages("Hash : %s , Symbol path : %s\n", ModuleSymbolGuidAndAge, ModuleSymbolPath); // } else { IsSymbolPdbDetailAvailable = FALSE; // // ShowMessages("err, unable to get module pdb details\n"); // } // // Build the structure for this module // ModuleSymDetailArray[IndexInSymbolBuffer].BaseAddress = (UINT64)ModuleInfo->Modules[i].ImageBase; memcpy(ModuleSymDetailArray[IndexInSymbolBuffer].FilePath, ModuleFullPath.c_str(), ModuleFullPath.size()); if (IsSymbolPdbDetailAvailable) { ModuleSymDetailArray[IndexInSymbolBuffer].IsSymbolDetailsFound = TRUE; memcpy(ModuleSymDetailArray[IndexInSymbolBuffer].ModuleSymbolGuidAndAge, ModuleSymbolGuidAndAge, MAXIMUM_GUID_AND_AGE_SIZE); memcpy(ModuleSymDetailArray[IndexInSymbolBuffer].ModuleSymbolPath, ModuleSymbolPath, MAX_PATH); // // Check if pdb file name is a real path or a module name // string ModuleSymbolPathString(ModuleSymbolPath); if (ModuleSymbolPathString.find(":\\") != std::string::npos) ModuleSymDetailArray[IndexInSymbolBuffer].IsLocalSymbolPath = TRUE; else ModuleSymDetailArray[IndexInSymbolBuffer].IsLocalSymbolPath = FALSE; } else { ModuleSymDetailArray[IndexInSymbolBuffer].IsSymbolDetailsFound = FALSE; } // // Check if it should be send to the remote debugger over serial // and also make sure that we're connected to the remote debugger // and this is a debuggee // if (SendOverSerial) { KdSendSymbolDetailPacket(&ModuleSymDetailArray[IndexInSymbolBuffer], IndexInSymbolBuffer, ModuleInfo->NumberOfModules + ModulesCount); } } // // ---------------------------------------------------------------------------------- // // // Store the buffer and length of module symbols details // *BufferToStoreDetails = ModuleSymDetailArray; *StoredLength = (ModuleInfo->NumberOfModules + ModulesCount) * sizeof(MODULE_SYMBOL_DETAIL); free(ModuleInfo); if (IsFreeUsermodeModulesBuffer) { free(ModuleDetailsRequest); } return TRUE; } /** * @brief Allocate (build) and update the symbol table whenever a debuggee is attached * on the debugger mode * * @param SymbolDetail Pointer to a buffer that was received as the single * symbol info * * @return BOOLEAN shows whether the operation was successful or not */ BOOLEAN SymbolBuildAndUpdateSymbolTable(PMODULE_SYMBOL_DETAIL SymbolDetail) { // // Check to avoid overflow in symbol table // if (g_SymbolTableCurrentIndex >= MAXIMUM_SUPPORTED_SYMBOLS) { ShowMessages("err, the symbol table buffer is full, unable to add new symbol\n"); return FALSE; } // // Check if we found an already built symbol table // if (g_SymbolTable == NULL) { // // Allocate Details buffer // g_SymbolTable = (PMODULE_SYMBOL_DETAIL)malloc(MAXIMUM_SUPPORTED_SYMBOLS * sizeof(MODULE_SYMBOL_DETAIL)); if (g_SymbolTable == NULL) { ShowMessages("err, unable to allocate memory for module list (%x)\n", GetLastError()); return FALSE; } // // Reset the index // g_SymbolTableCurrentIndex = 0; // // Make sure buffer is zero // RtlZeroMemory(g_SymbolTable, MAXIMUM_SUPPORTED_SYMBOLS * sizeof(MODULE_SYMBOL_DETAIL)); } // // Move it to the new buffer // memcpy(&g_SymbolTable[g_SymbolTableCurrentIndex], SymbolDetail, sizeof(MODULE_SYMBOL_DETAIL)); // // Add to index for future symbols // g_SymbolTableCurrentIndex++; // // Compute the (new) current size // g_SymbolTableSize = g_SymbolTableCurrentIndex * sizeof(MODULE_SYMBOL_DETAIL); return TRUE; } /** * @brief Update the symbol table from remote debuggee in debugger mode * @param ProcessId * * @return BOOLEAN shows whether the operation was successful or not */ BOOLEAN SymbolReloadSymbolTableInDebuggerMode(UINT32 ProcessId) { // // Check if we found an already built symbol table // SymbolDeleteSymTable(); // // Request to send new symbol details // if (KdSendSymbolReloadPacketToDebuggee(ProcessId)) { ShowMessages("symbol table updated successfully\n"); return TRUE; } else { return FALSE; } } /** * @brief Check and allocate module information * @details The caller should free the buffer * @param Modules * * @return BOOLEAN */ BOOLEAN SymbolCheckAndAllocateModuleInformation(PRTL_PROCESS_MODULES * Modules) { NTSTATUS Status = STATUS_UNSUCCESSFUL; ULONG SysModuleInfoBufferSize = 0; // // Enable Debug privilege to the current token // if (!WindowsSetDebugPrivilege()) { ShowMessages("err, couldn't set debug privilege\n"); return FALSE; } // // Get required size of "RTL_PROCESS_MODULES" buffer // Status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, NULL, NULL, &SysModuleInfoBufferSize); // // print the size of the buffer // // ShowMessages("size of the buffer : %x\n", SysModuleInfoBufferSize); *Modules = (PRTL_PROCESS_MODULES)malloc(SysModuleInfoBufferSize); if (*Modules == NULL) { ShowMessages("err, unable to allocate memory for module list (%x)\n", GetLastError()); return FALSE; } // // Get the module list // Status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, *Modules, SysModuleInfoBufferSize, NULL); if (!NT_SUCCESS(Status)) { ShowMessages("err, unable to query module list (%x)\n", Status); free(*Modules); return FALSE; } return TRUE; }