/** * @file DebuggerCommands.c * @author Sina Karvandi (sina@hyperdbg.org) * @author Alee Amini (alee@hyperdbg.org) * @brief Implementation of Debugger Commands * * @version 0.1 * @date 2020-04-23 * * @copyright This project is released under the GNU Public License v3. * */ #include "pch.h" /** * @brief read registers * @param Regs * @param ReadRegisterRequest * * @return BOOLEAN */ BOOLEAN DebuggerCommandReadRegisters(GUEST_REGS * Regs, PDEBUGGEE_REGISTER_READ_DESCRIPTION ReadRegisterRequest) { GUEST_EXTRA_REGISTERS ERegs = {0}; if (ReadRegisterRequest->RegisterId == DEBUGGEE_SHOW_ALL_REGISTERS) { // // Add General purpose registers // memcpy((PVOID)((CHAR *)ReadRegisterRequest + sizeof(DEBUGGEE_REGISTER_READ_DESCRIPTION)), Regs, sizeof(GUEST_REGS)); // // Read Extra registers // ERegs.CS = (UINT16)DebuggerGetRegValueWrapper(NULL, REGISTER_CS); ERegs.SS = (UINT16)DebuggerGetRegValueWrapper(NULL, REGISTER_SS); ERegs.DS = (UINT16)DebuggerGetRegValueWrapper(NULL, REGISTER_DS); ERegs.ES = (UINT16)DebuggerGetRegValueWrapper(NULL, REGISTER_ES); ERegs.FS = (UINT16)DebuggerGetRegValueWrapper(NULL, REGISTER_FS); ERegs.GS = (UINT16)DebuggerGetRegValueWrapper(NULL, REGISTER_GS); ERegs.RFLAGS = DebuggerGetRegValueWrapper(NULL, REGISTER_RFLAGS); ERegs.RIP = DebuggerGetRegValueWrapper(NULL, REGISTER_RIP); // // copy at the end of ReadRegisterRequest structure // memcpy((PVOID)((CHAR *)ReadRegisterRequest + sizeof(DEBUGGEE_REGISTER_READ_DESCRIPTION) + sizeof(GUEST_REGS)), &ERegs, sizeof(GUEST_EXTRA_REGISTERS)); } else { ReadRegisterRequest->Value = DebuggerGetRegValueWrapper(Regs, ReadRegisterRequest->RegisterId); } return TRUE; } /** * @brief Read memory for different commands * * @param ReadMemRequest request structure for reading memory * @param UserBuffer user buffer to copy the memory * @param ReturnSize size that should be returned to user mode buffers * * @return BOOLEAN */ BOOLEAN DebuggerCommandReadMemory(PDEBUGGER_READ_MEMORY ReadMemRequest, PVOID UserBuffer, PSIZE_T ReturnSize) { UINT32 Pid; UINT32 Size; UINT64 Address; DEBUGGER_READ_MEMORY_TYPE MemType; BOOLEAN Is32BitProcess = FALSE; // // Adjust the parameters // Pid = ReadMemRequest->Pid; Size = ReadMemRequest->Size; Address = ReadMemRequest->Address; MemType = ReadMemRequest->MemoryType; if (Size && Address != (UINT64)NULL) { if (MemoryManagerReadProcessMemoryNormal((HANDLE)Pid, (PVOID)Address, MemType, (PVOID)UserBuffer, Size, ReturnSize)) { // // Reading memory was successful // // // *** Now, we check whether this a disassembly request for a virtual address // or not, if so, we'll detect whether the target process is 32-bit or 64-bit *** // // // Check if the address is on a 32-bit mode process or not (just in case of disassembling) // if (ReadMemRequest->MemoryType == DEBUGGER_READ_VIRTUAL_ADDRESS && ReadMemRequest->GetAddressMode) { // // Check if the address is in the canonical range for kernel space // if (ReadMemRequest->Address >= 0xFFFF800000000000 && ReadMemRequest->Address <= 0xFFFFFFFFFFFFFFFF) { // // The address is in the range of canonical kernel space, so it's 64-bit process // ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_64_BIT; } else { // // The address is in the user-mode and the memory type is a virtual address // for disassembly, so we have to query whether the target process is a // 32-bit process or a 64-bit process // if (UserAccessIsWow64Process((HANDLE)ReadMemRequest->Pid, &Is32BitProcess)) { if (Is32BitProcess) { ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_32_BIT; } else { ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_64_BIT; } } else { // // We couldn't determine the type of process, let's assume that it's a // 64-bit process by default // ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_64_BIT; } } } // // Anyway, the read was successful // ReadMemRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return TRUE; } else { // // Reading memory was not successful // ReadMemRequest->KernelStatus = DEBUGGER_ERROR_READING_MEMORY_INVALID_PARAMETER; return FALSE; } } else { // // Parameters are invalid // ReadMemRequest->KernelStatus = DEBUGGER_ERROR_READING_MEMORY_INVALID_PARAMETER; return FALSE; } } /** * @brief Read memory for different commands from vmxroot mode * * @param ReadMemRequest request structure for reading memory * @param UserBuffer user buffer to copy the memory * @param ReturnSize size that should be returned to user mode buffers * @return BOOLEAN */ BOOLEAN DebuggerCommandReadMemoryVmxRoot(PDEBUGGER_READ_MEMORY ReadMemRequest, UCHAR * UserBuffer, UINT32 * ReturnSize) { UINT32 Pid; UINT32 Size; UINT64 Address; UINT64 OffsetInUserBuffer; DEBUGGER_READ_MEMORY_TYPE MemType; BOOLEAN Is32BitProcess = FALSE; PLIST_ENTRY TempList = 0; Pid = ReadMemRequest->Pid; Size = ReadMemRequest->Size; Address = ReadMemRequest->Address; MemType = ReadMemRequest->MemoryType; // // read memory safe // if (MemType == DEBUGGER_READ_PHYSICAL_ADDRESS) { // // Check whether the physical memory is valid or not // if (!CheckAddressPhysical(Address)) { ReadMemRequest->KernelStatus = DEBUGGER_ERROR_INVALID_PHYSICAL_ADDRESS; return FALSE; } MemoryMapperReadMemorySafeByPhysicalAddress(Address, (UINT64)UserBuffer, Size); } else if (MemType == DEBUGGER_READ_VIRTUAL_ADDRESS) { // // Check whether the virtual memory is available in the current // memory layout and also is present in the RAM // if (!CheckAccessValidityAndSafety(Address, Size)) { ReadMemRequest->KernelStatus = DEBUGGER_ERROR_INVALID_ADDRESS; return FALSE; } // // Read memory safely // MemoryMapperReadMemorySafeOnTargetProcess(Address, UserBuffer, Size); // // Check if the target memory is filled with breakpoint of the 'bp' commands // if the memory is changed due to this command, then we'll changes it to // the previous byte // // // Iterate through the breakpoint list // TempList = &g_BreakpointsListHead; while (&g_BreakpointsListHead != TempList->Flink) { TempList = TempList->Flink; PDEBUGGEE_BP_DESCRIPTOR CurrentBreakpointDesc = CONTAINING_RECORD(TempList, DEBUGGEE_BP_DESCRIPTOR, BreakpointsList); if (CurrentBreakpointDesc->Address >= Address && CurrentBreakpointDesc->Address <= Address + Size) { // // The address is found, we have to swap the byte if the target // byte is 0xcc // // // Find the address location at user buffer // OffsetInUserBuffer = CurrentBreakpointDesc->Address - Address; if (UserBuffer[OffsetInUserBuffer] == 0xcc) { UserBuffer[OffsetInUserBuffer] = CurrentBreakpointDesc->PreviousByte; } } } } else { ReadMemRequest->KernelStatus = DEBUGGER_ERROR_MEMORY_TYPE_INVALID; return FALSE; } // // Check if the address is on a 32-bit mode process or not (just in case of disassembling) // if (ReadMemRequest->MemoryType == DEBUGGER_READ_VIRTUAL_ADDRESS && ReadMemRequest->GetAddressMode) { // // Check if the address is in the canonical range for kernel space // if (ReadMemRequest->Address >= 0xFFFF800000000000 && ReadMemRequest->Address <= 0xFFFFFFFFFFFFFFFF) { // // The address is in the range of canonical kernel space, so it's 64-bit process // ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_64_BIT; } else { // // The address is in the user-mode and the memory type is a virtual address // for disassembly, so we have to query whether the target process is a // 32-bit process or a 64-bit process // if (UserAccessIsWow64ProcessByEprocess(PsGetCurrentProcess(), &Is32BitProcess)) { if (Is32BitProcess) { ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_32_BIT; } else { ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_64_BIT; } } else { // // We couldn't determine the type of process, let's assume that it's a // 64-bit process by default // ReadMemRequest->AddressMode = DEBUGGER_READ_ADDRESS_MODE_64_BIT; } } } // // Set the final status of memory read as it was successful // ReadMemRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; *ReturnSize = Size; return TRUE; } /** * @brief Perform rdmsr, wrmsr commands * * @param ReadOrWriteMsrRequest Msr read/write request * @param UserBuffer user buffer to save the results * @param ReturnSize return size to user-mode buffers * @return NTSTATUS */ NTSTATUS DebuggerReadOrWriteMsr(PDEBUGGER_READ_AND_WRITE_ON_MSR ReadOrWriteMsrRequest, UINT64 * UserBuffer, PSIZE_T ReturnSize) { NTSTATUS Status; ULONG ProcessorsCount; ProcessorsCount = KeQueryActiveProcessorCount(0); // // We don't check whether the MSR is in valid range of hardware or not // because the user might send a non-valid MSR which means sth to the // Windows or VMM, e.g the range specified for VMMs in Hyper-v // if (ReadOrWriteMsrRequest->ActionType == DEBUGGER_MSR_WRITE) { // // Set Msr to be applied on the target cores // if (ReadOrWriteMsrRequest->CoreNumber == DEBUGGER_READ_AND_WRITE_ON_MSR_APPLY_ALL_CORES) { // // Means that we should apply it on all cores // for (SIZE_T i = 0; i < ProcessorsCount; i++) { g_DbgState[i].MsrState.Msr = ReadOrWriteMsrRequest->Msr; g_DbgState[i].MsrState.Value = ReadOrWriteMsrRequest->Value; } // // Broadcast to all cores to change their Msrs // KeGenericCallDpc(DpcRoutineWriteMsrToAllCores, 0x0); } else { // // We have to change a single core's msr // // // Check if the core number is not invalid // if (ReadOrWriteMsrRequest->CoreNumber >= ProcessorsCount) { return STATUS_INVALID_PARAMETER; } // // Otherwise it's valid // g_DbgState[ReadOrWriteMsrRequest->CoreNumber].MsrState.Msr = ReadOrWriteMsrRequest->Msr; g_DbgState[ReadOrWriteMsrRequest->CoreNumber].MsrState.Value = ReadOrWriteMsrRequest->Value; // // Execute it on a single core // Status = DpcRoutineRunTaskOnSingleCore(ReadOrWriteMsrRequest->CoreNumber, (PVOID)DpcRoutinePerformWriteMsr, NULL); *ReturnSize = 0; return Status; } // // It's an wrmsr, nothing to return // *ReturnSize = 0; return STATUS_SUCCESS; } else if (ReadOrWriteMsrRequest->ActionType == DEBUGGER_MSR_READ) { // // Set Msr to be applied on the target cores // if (ReadOrWriteMsrRequest->CoreNumber == DEBUGGER_READ_AND_WRITE_ON_MSR_APPLY_ALL_CORES) { // // Means that we should apply it on all cores // for (SIZE_T i = 0; i < ProcessorsCount; i++) { g_DbgState[i].MsrState.Msr = ReadOrWriteMsrRequest->Msr; } // // Broadcast to all cores to read their Msrs // KeGenericCallDpc(DpcRoutineReadMsrToAllCores, 0x0); // // When we reach here, all processors read their shits // so we have to fill that fucking buffer for user mode // for (SIZE_T i = 0; i < ProcessorsCount; i++) { UserBuffer[i] = g_DbgState[i].MsrState.Value; } // // It's an rdmsr we have to return a value for all cores // *ReturnSize = sizeof(UINT64) * ProcessorsCount; return STATUS_SUCCESS; } else { // // Apply to one core // // // Check if the core number is not invalid // if (ReadOrWriteMsrRequest->CoreNumber >= ProcessorsCount) { *ReturnSize = 0; return STATUS_INVALID_PARAMETER; } // // Otherwise it's valid // g_DbgState[ReadOrWriteMsrRequest->CoreNumber].MsrState.Msr = ReadOrWriteMsrRequest->Msr; // // Execute it on a single core // Status = DpcRoutineRunTaskOnSingleCore(ReadOrWriteMsrRequest->CoreNumber, (PVOID)DpcRoutinePerformReadMsr, NULL); if (Status != STATUS_SUCCESS) { *ReturnSize = 0; return Status; } // // Restore the result to the usermode // UserBuffer[0] = g_DbgState[ReadOrWriteMsrRequest->CoreNumber].MsrState.Value; *ReturnSize = sizeof(UINT64); return STATUS_SUCCESS; } } *ReturnSize = 0; return STATUS_UNSUCCESSFUL; } /** * @brief Edit physical and virtual memory * * @param EditMemRequest edit memory request * @return NTSTATUS */ NTSTATUS DebuggerCommandEditMemory(PDEBUGGER_EDIT_MEMORY EditMemRequest) { UINT32 LengthOfEachChunk = 0; PVOID DestinationAddress = 0; PVOID SourceAddress = 0; // // set chunk size in each modification // if (EditMemRequest->ByteSize == EDIT_BYTE) { LengthOfEachChunk = 1; } else if (EditMemRequest->ByteSize == EDIT_DWORD) { LengthOfEachChunk = 4; } else if (EditMemRequest->ByteSize == EDIT_QWORD) { LengthOfEachChunk = 8; } else { // // Invalid parameter // EditMemRequest->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_PARAMETER; return STATUS_UNSUCCESSFUL; } // // Check if address is valid or not valid (virtual address) // if (EditMemRequest->MemoryType == EDIT_VIRTUAL_MEMORY) { if (EditMemRequest->ProcessId == HANDLE_TO_UINT32(PsGetCurrentProcessId()) && VirtualAddressToPhysicalAddress((PVOID)EditMemRequest->Address) == 0) { // // It's an invalid address in current process // EditMemRequest->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_ADDRESS_BASED_ON_CURRENT_PROCESS; return STATUS_UNSUCCESSFUL; } else if (VirtualAddressToPhysicalAddressByProcessId((PVOID)EditMemRequest->Address, EditMemRequest->ProcessId) == 0) { // // It's an invalid address in another process // EditMemRequest->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_ADDRESS_BASED_ON_OTHER_PROCESS; return STATUS_UNSUCCESSFUL; } // // Edit the memory // for (SIZE_T i = 0; i < EditMemRequest->CountOf64Chunks; i++) { DestinationAddress = (PVOID)((UINT64)EditMemRequest->Address + (i * LengthOfEachChunk)); SourceAddress = (PVOID)((UINT64)EditMemRequest + SIZEOF_DEBUGGER_EDIT_MEMORY + (i * sizeof(UINT64))); // // Instead of directly accessing the memory we use the MemoryMapperWriteMemorySafe // It is because the target page might be read-only so we can make it writable // // RtlCopyBytes(DestinationAddress, SourceAddress, LengthOfEachChunk); MemoryMapperWriteMemoryUnsafe((UINT64)DestinationAddress, SourceAddress, LengthOfEachChunk, EditMemRequest->ProcessId); } } else if (EditMemRequest->MemoryType == EDIT_PHYSICAL_MEMORY) { // // Check whether the physical address // if (!CheckAddressPhysical(EditMemRequest->Address)) { EditMemRequest->Result = DEBUGGER_ERROR_INVALID_ADDRESS; return STATUS_UNSUCCESSFUL; } // // Edit the physical memory // for (SIZE_T i = 0; i < EditMemRequest->CountOf64Chunks; i++) { DestinationAddress = (PVOID)((UINT64)EditMemRequest->Address + (i * LengthOfEachChunk)); SourceAddress = (PVOID)((UINT64)EditMemRequest + SIZEOF_DEBUGGER_EDIT_MEMORY + (i * sizeof(UINT64))); // MemoryMapperWriteMemorySafeByPhysicalAddress((UINT64)DestinationAddress, (UINT64)SourceAddress, LengthOfEachChunk); // WritePhysicalMemoryUsingMapIoSpace((PVOID)SourceAddress, (PVOID)DestinationAddress, LengthOfEachChunk); if (MemoryManagerWritePhysicalMemoryNormal((PVOID)DestinationAddress, (PVOID)SourceAddress, (SIZE_T)LengthOfEachChunk) == FALSE) { EditMemRequest->Result = DEBUGGER_ERROR_INVALID_ADDRESS; return STATUS_UNSUCCESSFUL; } } } else { // // Invalid parameter // EditMemRequest->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_PARAMETER; return STATUS_UNSUCCESSFUL; } // // Set the results // EditMemRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief Edit physical and virtual memory on vmxroot mode * * @param EditMemRequest edit memory request * @return NTSTATUS */ BOOLEAN DebuggerCommandEditMemoryVmxRoot(PDEBUGGER_EDIT_MEMORY EditMemRequest) { UINT32 LengthOfEachChunk = 0; PVOID DestinationAddress = 0; PVOID SourceAddress = 0; // // THIS FUNCTION IS SAFE TO BE CALLED FROM VMX-ROOT // // // set chunk size in each modification // if (EditMemRequest->ByteSize == EDIT_BYTE) { LengthOfEachChunk = 1; } else if (EditMemRequest->ByteSize == EDIT_DWORD) { LengthOfEachChunk = 4; } else if (EditMemRequest->ByteSize == EDIT_QWORD) { LengthOfEachChunk = 8; } else { // // Invalid parameter // EditMemRequest->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_PARAMETER; return FALSE; } if (EditMemRequest->MemoryType == EDIT_VIRTUAL_MEMORY) { // // Check whether the virtual memory is available in the current // memory layout and also is present in the RAM // if (!CheckAccessValidityAndSafety(EditMemRequest->Address, EditMemRequest->ByteSize * EditMemRequest->CountOf64Chunks)) { EditMemRequest->Result = DEBUGGER_ERROR_INVALID_ADDRESS; return FALSE; } // // Edit the memory // for (SIZE_T i = 0; i < EditMemRequest->CountOf64Chunks; i++) { DestinationAddress = (PVOID)((UINT64)EditMemRequest->Address + (i * LengthOfEachChunk)); SourceAddress = (PVOID)((UINT64)EditMemRequest + SIZEOF_DEBUGGER_EDIT_MEMORY + (i * sizeof(UINT64))); // // Instead of directly accessing the memory we use the MemoryMapperWriteMemorySafeOnTargetProcess // It is because the target page might be read-only so we can make it writable // // RtlCopyBytes(DestinationAddress, SourceAddress, LengthOfEachChunk); MemoryMapperWriteMemorySafeOnTargetProcess((UINT64)DestinationAddress, SourceAddress, LengthOfEachChunk); } } else if (EditMemRequest->MemoryType == EDIT_PHYSICAL_MEMORY) { // // Check whether the physical address // if (!CheckAddressPhysical(EditMemRequest->Address)) { EditMemRequest->Result = DEBUGGER_ERROR_INVALID_ADDRESS; return FALSE; } // // Edit the physical memory // for (SIZE_T i = 0; i < EditMemRequest->CountOf64Chunks; i++) { DestinationAddress = (PVOID)((UINT64)EditMemRequest->Address + (i * LengthOfEachChunk)); SourceAddress = (PVOID)((UINT64)EditMemRequest + SIZEOF_DEBUGGER_EDIT_MEMORY + (i * sizeof(UINT64))); MemoryMapperWriteMemorySafeByPhysicalAddress((UINT64)DestinationAddress, (UINT64)SourceAddress, LengthOfEachChunk); } } else { // // Invalid parameter // EditMemRequest->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_PARAMETER; return FALSE; } // // Set the results // EditMemRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return TRUE; } /** * @brief Search on virtual memory (not work on physical memory) * * @details This function can be called from vmx-root mode * Do NOT directly call this function as the virtual addresses * should be valid on the target process memory layout * instead call : SearchAddressWrapper * the address between StartAddress and EndAddress should be contiguous * * @param AddressToSaveResults Address to save the search results * @param SearchMemRequest request structure of searching memory * @param StartAddress valid start address based on target process * @param EndAddress valid end address based on target process * @param IsDebuggeePaused Set to true when the search is performed in * the debugger mode * @param CountOfMatchedCases Number of matched cases * @return BOOLEAN Whether the search was successful or not */ BOOLEAN PerformSearchAddress(UINT64 * AddressToSaveResults, PDEBUGGER_SEARCH_MEMORY SearchMemRequest, UINT64 StartAddress, UINT64 EndAddress, BOOLEAN IsDebuggeePaused, PUINT32 CountOfMatchedCases) { UINT32 CountOfOccurance = 0; UINT64 Cmp64 = 0; UINT32 IndexToArrayOfResults = 0; UINT32 LengthOfEachChunk = 0; PVOID TempSourceAddress = 0; PVOID SourceAddress = 0; BOOLEAN StillMatch = FALSE; UINT64 TempValue = (UINT64)NULL; CR3_TYPE CurrentProcessCr3 = {0}; // // set chunk size in each modification // if (SearchMemRequest->ByteSize == SEARCH_BYTE) { LengthOfEachChunk = 1; } else if (SearchMemRequest->ByteSize == SEARCH_DWORD) { LengthOfEachChunk = 4; } else if (SearchMemRequest->ByteSize == SEARCH_QWORD) { LengthOfEachChunk = 8; } else { // // Invalid parameter // return FALSE; } // // Check if address is virtual address or physical address // if (SearchMemRequest->MemoryType == SEARCH_VIRTUAL_MEMORY || SearchMemRequest->MemoryType == SEARCH_PHYSICAL_FROM_VIRTUAL_MEMORY) { // // Search the memory // // // Change the memory layout (cr3), if the user specified a // special process // if (IsDebuggeePaused) { // // Switch to target process memory layout // CurrentProcessCr3 = SwitchToProcessMemoryLayoutByCr3(LayoutGetCurrentProcessCr3()); } else { if (SearchMemRequest->ProcessId != HANDLE_TO_UINT32(PsGetCurrentProcessId())) { CurrentProcessCr3 = SwitchToProcessMemoryLayout(SearchMemRequest->ProcessId); } } // // Here we iterate through the buffer we received from // user-mode // SourceAddress = (PVOID)((UINT64)SearchMemRequest + SIZEOF_DEBUGGER_SEARCH_MEMORY); for (SIZE_T BaseIterator = (SIZE_T)StartAddress; BaseIterator < ((UINT64)EndAddress); BaseIterator += LengthOfEachChunk) { // // *** Search the memory *** // // // Copy 64bit, 32bit or one byte value into Cmp64 buffer and then compare it // Check if we should access the memory directly, or through safe memory // routine from vmx-root // if (IsDebuggeePaused) { MemoryMapperReadMemorySafe((UINT64)BaseIterator, &Cmp64, LengthOfEachChunk); } else { RtlCopyMemory(&Cmp64, (PVOID)BaseIterator, LengthOfEachChunk); } // // Get the searching bytes // TempValue = *(UINT64 *)SourceAddress; // // Check whether the byte matches the source or not // if (Cmp64 == TempValue) { // // Indicate that it matches until now // StillMatch = TRUE; // // Try to check each element (we don't start from the very first element as // it checked before ) // for (SIZE_T i = LengthOfEachChunk; i < SearchMemRequest->CountOf64Chunks; i++) { // // I know, we have a double check here ;) // TempSourceAddress = (PVOID)((UINT64)SearchMemRequest + SIZEOF_DEBUGGER_SEARCH_MEMORY + (i * sizeof(UINT64))); // // Add i to BaseIterator and recompute the Cmp64 // Check if we should access the memory directly, or through safe memory // routine from vmx-root // if (IsDebuggeePaused) { MemoryMapperReadMemorySafe((UINT64)(BaseIterator + (LengthOfEachChunk * i)), &Cmp64, LengthOfEachChunk); } else { RtlCopyMemory(&Cmp64, (PVOID)(BaseIterator + (LengthOfEachChunk * i)), LengthOfEachChunk); } // // Check if we should access the memory directly, // or through safe memory routine from vmx-root // if (IsDebuggeePaused) { MemoryMapperReadMemorySafe((UINT64)TempSourceAddress, &TempValue, sizeof(UINT64)); } else { TempValue = *(UINT64 *)TempSourceAddress; } if (!(Cmp64 == TempValue)) { // // One thing didn't match so this is not the pattern // StillMatch = FALSE; // // Break from the loop // break; } } // // Check if we find the pattern or not // if (StillMatch) { // // We found the a matching address, let's save the // address for future use // CountOfOccurance++; if (IsDebuggeePaused) { if (SearchMemRequest->MemoryType == SEARCH_PHYSICAL_FROM_VIRTUAL_MEMORY) { // // It's a physical memory // Log("%llx\n", VirtualAddressToPhysicalAddress((PVOID)BaseIterator)); } else { // // It's a virtual memory // Log("%llx\n", BaseIterator); } } else { if (SearchMemRequest->MemoryType == SEARCH_PHYSICAL_FROM_VIRTUAL_MEMORY) { // // It's a physical memory // AddressToSaveResults[IndexToArrayOfResults] = VirtualAddressToPhysicalAddress((PVOID)BaseIterator); } else { // // It's a virtual memory // AddressToSaveResults[IndexToArrayOfResults] = BaseIterator; } } // // Increase the array pointer if it doesn't exceed the limitation // if (MaximumSearchResults > IndexToArrayOfResults) { IndexToArrayOfResults++; } else { // // The result buffer is full! // *CountOfMatchedCases = CountOfOccurance; return TRUE; } } } else { // // Not found in the place // continue; } } // // Restore the previous memory layout (cr3), if the user specified a // special process // if (IsDebuggeePaused || SearchMemRequest->ProcessId != HANDLE_TO_UINT32(PsGetCurrentProcessId())) { SwitchToPreviousProcess(CurrentProcessCr3); } } else if (SearchMemRequest->MemoryType == SEARCH_PHYSICAL_MEMORY) { // // That's an error, the physical memory is handled like virtual memory and // thus we should never reach here // LogError("Err, searching physical memory is not allowed without virtual address"); return FALSE; } else { // // Invalid parameter // return FALSE; } // // As we're here the search is finished without error // *CountOfMatchedCases = CountOfOccurance; return TRUE; } /** * @brief The wrapper to check for validity of addresses and call * the search routines for both physical and virtual memory * * @details This function can be called from vmx-root mode * The address between start address and end address will be checked * to make a contiguous address * * @param AddressToSaveResults Address to save the search results * @param SearchMemRequest request structure of searching memory * @param StartAddress start address of searching based on target process * @param EndAddress start address of searching based on target process * @param IsDebuggeePaused Set to true when the search is performed in * the debugger mode * @param CountOfMatchedCases Number of matched cases * @return BOOLEAN Whether there was any error or not */ BOOLEAN SearchAddressWrapper(PUINT64 AddressToSaveResults, PDEBUGGER_SEARCH_MEMORY SearchMemRequest, UINT64 StartAddress, UINT64 EndAddress, BOOLEAN IsDebuggeePaused, PUINT32 CountOfMatchedCases) { CR3_TYPE CurrentProcessCr3; UINT64 BaseAddress = 0; UINT64 RealPhysicalAddress = 0; UINT64 TempValue = (UINT64)NULL; UINT64 TempStartAddress = (UINT64)NULL; BOOLEAN DoesBaseAddrSaved = FALSE; BOOLEAN SearchResult = FALSE; // // Reset the count of matched cases // *CountOfMatchedCases = 0; if (SearchMemRequest->MemoryType == SEARCH_VIRTUAL_MEMORY) { // // It's a virtual address search // // // Align the page and search with alignment // TempStartAddress = StartAddress; StartAddress = (UINT64)PAGE_ALIGN(StartAddress); if (IsDebuggeePaused) { // // Switch to new process's memory layout // CurrentProcessCr3 = SwitchToProcessMemoryLayoutByCr3(LayoutGetCurrentProcessCr3()); } else { // // Switch to new process's memory layout // CurrentProcessCr3 = SwitchToProcessMemoryLayout(SearchMemRequest->ProcessId); } // // We will try to find a contigues address // while (StartAddress < EndAddress) { // // Check if address is valid or not // Generally, we can use VirtualAddressToPhysicalAddressByProcessId // but let's not change the cr3 multiple times // TempValue = VirtualAddressToPhysicalAddress((PVOID)StartAddress); if (TempValue != 0) { // // Address is valid, let's add a page size to it // nothing to do // if (!DoesBaseAddrSaved) { BaseAddress = TempStartAddress; DoesBaseAddrSaved = TRUE; } } else { // // Address is not valid anymore // break; } // // Make the start address ready for next address // StartAddress += PAGE_SIZE; } // // Restore the original process // SwitchToPreviousProcess(CurrentProcessCr3); // // All of the address chunk was valid // if (DoesBaseAddrSaved && StartAddress > BaseAddress) { SearchResult = PerformSearchAddress(AddressToSaveResults, SearchMemRequest, BaseAddress, EndAddress, IsDebuggeePaused, CountOfMatchedCases); } else { // // There was an error, address was probably not contiguous // return FALSE; } } else if (SearchMemRequest->MemoryType == SEARCH_PHYSICAL_MEMORY) { // // when we reached here, we know that it's a valid physical memory, // so we change the structure and pass it as a virtual address to // the search function // RealPhysicalAddress = SearchMemRequest->Address; // // Change the start address // if (IsDebuggeePaused) { SearchMemRequest->Address = PhysicalAddressToVirtualAddressOnTargetProcess((PVOID)StartAddress); EndAddress = PhysicalAddressToVirtualAddressOnTargetProcess((PVOID)EndAddress); } else if (SearchMemRequest->ProcessId == HANDLE_TO_UINT32(PsGetCurrentProcessId())) { SearchMemRequest->Address = PhysicalAddressToVirtualAddress(StartAddress); EndAddress = PhysicalAddressToVirtualAddress(EndAddress); } else { SearchMemRequest->Address = PhysicalAddressToVirtualAddressByProcessId((PVOID)StartAddress, SearchMemRequest->ProcessId); EndAddress = PhysicalAddressToVirtualAddressByProcessId((PVOID)EndAddress, SearchMemRequest->ProcessId); } // // Change the type of memory // SearchMemRequest->MemoryType = SEARCH_PHYSICAL_FROM_VIRTUAL_MEMORY; // // Call the wrapper // SearchResult = PerformSearchAddress(AddressToSaveResults, SearchMemRequest, SearchMemRequest->Address, EndAddress, IsDebuggeePaused, CountOfMatchedCases); // // Restore the previous state // SearchMemRequest->MemoryType = SEARCH_PHYSICAL_MEMORY; SearchMemRequest->Address = RealPhysicalAddress; } return SearchResult; } /** * @brief Start searching memory * * @param SearchMemRequest Request to search memory * @return NTSTATUS */ NTSTATUS DebuggerCommandSearchMemory(PDEBUGGER_SEARCH_MEMORY SearchMemRequest) { PUINT64 SearchResultsStorage = NULL; PUINT64 UsermodeBuffer = NULL; UINT64 AddressFrom = 0; UINT64 AddressTo = 0; UINT64 CurrentValue = 0; UINT32 ResultsIndex = 0; UINT32 CountOfResults = 0; // // Check if process id is valid or not // if (SearchMemRequest->ProcessId != HANDLE_TO_UINT32(PsGetCurrentProcessId()) && !CommonIsProcessExist(SearchMemRequest->ProcessId)) { return STATUS_INVALID_PARAMETER; } // // User-mode buffer is same as SearchMemRequest // UsermodeBuffer = (UINT64 *)SearchMemRequest; // // We store the user-mode data in a separate variable because // we will use them later when we Zeroed the SearchMemRequest // AddressFrom = SearchMemRequest->Address; AddressTo = SearchMemRequest->Address + SearchMemRequest->Length; // // We support up to MaximumSearchResults search results // SearchResultsStorage = PlatformMemAllocateZeroedNonPagedPool(MaximumSearchResults * sizeof(UINT64)); if (SearchResultsStorage == NULL) { // // Not enough memory // return STATUS_INSUFFICIENT_RESOURCES; } // // Call the wrapper // SearchAddressWrapper(SearchResultsStorage, SearchMemRequest, AddressFrom, AddressTo, FALSE, &CountOfResults); // // In this point, we to store the results (if any) to the user-mode // buffer SearchMemRequest itself is the user-mode buffer and we also // checked from the previous function that the output buffer is at // least SearchMemRequest bigger or equal to MaximumSearchResults * sizeof(UINT64) // so we need to clear everything here, and also we should keep in mind that // SearchMemRequest is no longer valid // RtlZeroMemory(SearchMemRequest, MaximumSearchResults * sizeof(UINT64)); // // It's time to move the results from our temporary buffer to the user-mode // buffer, also there is something that we should check and that's the fact // that we used aligned page addresses so the results should be checked to // see whether the results are between the user's entered addresses or not // for (SIZE_T i = 0; i < MaximumSearchResults; i++) { CurrentValue = SearchResultsStorage[i]; if (CurrentValue == (UINT64)NULL) { // // Nothing left to move // break; } if (CurrentValue >= AddressFrom && CurrentValue <= AddressTo) { // // Move the variable // UsermodeBuffer[ResultsIndex] = CurrentValue; ResultsIndex++; } } // // Free the results pool // PlatformMemFreePool(SearchResultsStorage); return STATUS_SUCCESS; } /** * @brief Perform the flush requests to vmx-root and vmx non-root buffers * * @param DebuggerFlushBuffersRequest Request to flush the buffers * @return NTSTATUS */ NTSTATUS DebuggerCommandFlush(PDEBUGGER_FLUSH_LOGGING_BUFFERS DebuggerFlushBuffersRequest) { // // We try to flush buffers for both vmx-root and regular kernel buffer // DebuggerFlushBuffersRequest->CountOfMessagesThatSetAsReadFromVmxRoot = LogMarkAllAsRead(TRUE); DebuggerFlushBuffersRequest->CountOfMessagesThatSetAsReadFromVmxNonRoot = LogMarkAllAsRead(FALSE); DebuggerFlushBuffersRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief Perform the command finished signal * * @param DebuggerFinishedExecutionRequest Request to * signal debuggee about execution state * @return NTSTATUS */ NTSTATUS DebuggerCommandSignalExecutionState(PDEBUGGER_SEND_COMMAND_EXECUTION_FINISHED_SIGNAL DebuggerFinishedExecutionRequest) { // // It's better to send the signal from vmx-root mode // VmFuncVmxVmcall(DEBUGGER_VMCALL_SIGNAL_DEBUGGER_EXECUTION_FINISHED, 0, 0, 0); DebuggerFinishedExecutionRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief Send the user-mode buffer to debugger * * @param DebuggerSendUsermodeMessageRequest Request to send message to debugger * @return NTSTATUS */ NTSTATUS DebuggerCommandSendMessage(PDEBUGGER_SEND_USERMODE_MESSAGES_TO_DEBUGGER DebuggerSendUsermodeMessageRequest) { // // It's better to send the signal from vmx-root mode to avoid deadlock // VmFuncVmxVmcall(DEBUGGER_VMCALL_SEND_MESSAGES_TO_DEBUGGER, (UINT64)DebuggerSendUsermodeMessageRequest + (SIZEOF_DEBUGGER_SEND_USERMODE_MESSAGES_TO_DEBUGGER), DebuggerSendUsermodeMessageRequest->Length, 0); DebuggerSendUsermodeMessageRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief Send general buffers from the debuggee to the debugger * * @param DebuggeeBufferRequest Request to buffer that will be sent to the debugger * @return NTSTATUS */ NTSTATUS DebuggerCommandSendGeneralBufferToDebugger(PDEBUGGEE_SEND_GENERAL_PACKET_FROM_DEBUGGEE_TO_DEBUGGER DebuggeeBufferRequest) { // // It's better to send the signal from vmx-root mode to avoid deadlock // VmFuncVmxVmcall(DEBUGGER_VMCALL_SEND_GENERAL_BUFFER_TO_DEBUGGER, (UINT64)DebuggeeBufferRequest, 0, 0); DebuggeeBufferRequest->KernelResult = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief Reserve and allocate pre-allocated buffers * * @param PreallocRequest Request details of needed buffers to be reserved * * @return NTSTATUS */ NTSTATUS DebuggerCommandReservePreallocatedPools(PDEBUGGER_PREALLOC_COMMAND PreallocRequest) { switch (PreallocRequest->Type) { case DEBUGGER_PREALLOC_COMMAND_TYPE_THREAD_INTERCEPTION: // // Request pages to be allocated for thread interception mechanism // PoolManagerRequestAllocation(sizeof(USERMODE_DEBUGGING_THREAD_HOLDER), PreallocRequest->Count, PROCESS_THREAD_HOLDER); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_MONITOR: // // Perform the allocations for the '!monitor' command // ConfigureEptHookAllocateExtraHookingPagesForMemoryMonitorsAndExecEptHooks(PreallocRequest->Count); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_EPTHOOK: // // Perform the allocations for the '!epthook' command // ConfigureEptHookAllocateExtraHookingPagesForMemoryMonitorsAndExecEptHooks(PreallocRequest->Count); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_EPTHOOK2: // // All the prealloc requests of regular EPT hooks are needed for the '!epthook2' // ConfigureEptHookReservePreallocatedPoolsForEptHooks(PreallocRequest->Count); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_REGULAR_EVENT: // // Request pages to be allocated for regular instant events // PoolManagerRequestAllocation(REGULAR_INSTANT_EVENT_CONDITIONAL_BUFFER, PreallocRequest->Count, INSTANT_REGULAR_EVENT_BUFFER); // // Request pages to be allocated for regular instant events's actions // PoolManagerRequestAllocation(REGULAR_INSTANT_EVENT_ACTION_BUFFER, PreallocRequest->Count, INSTANT_REGULAR_EVENT_ACTION_BUFFER); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_BIG_EVENT: // // Request pages to be allocated for big instant events // PoolManagerRequestAllocation(BIG_INSTANT_EVENT_CONDITIONAL_BUFFER, PreallocRequest->Count, INSTANT_BIG_EVENT_BUFFER); // // Request pages to be allocated for big instant events's actions // PoolManagerRequestAllocation(BIG_INSTANT_EVENT_ACTION_BUFFER, PreallocRequest->Count, INSTANT_BIG_EVENT_ACTION_BUFFER); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_REGULAR_SAFE_BUFFER: // // Request pages to be allocated for regular safe buffer ($buffer) for events // PoolManagerRequestAllocation(REGULAR_INSTANT_EVENT_REQUESTED_SAFE_BUFFER, PreallocRequest->Count, INSTANT_REGULAR_SAFE_BUFFER_FOR_EVENTS); break; case DEBUGGER_PREALLOC_COMMAND_TYPE_BIG_SAFE_BUFFER: // // Request pages to be allocated for big safe buffer ($buffer) for events // PoolManagerRequestAllocation(BIG_INSTANT_EVENT_REQUESTED_SAFE_BUFFER, PreallocRequest->Count, INSTANT_BIG_SAFE_BUFFER_FOR_EVENTS); break; default: PreallocRequest->KernelStatus = DEBUGGER_ERROR_COULD_NOT_FIND_ALLOCATION_TYPE; return STATUS_UNSUCCESSFUL; } // // Invalidate and perform the allocations as we're in PASSIVE_LEVEL // PoolManagerCheckAndPerformAllocationAndDeallocation(); PreallocRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief Preactivate a special functionality * * @param PreactivateRequest Request details of preactivation * * @return NTSTATUS */ NTSTATUS DebuggerCommandPreactivateFunctionality(PDEBUGGER_PREACTIVATE_COMMAND PreactivateRequest) { switch (PreactivateRequest->Type) { case DEBUGGER_PREACTIVATE_COMMAND_TYPE_MODE: // // Request for enabling the mode mechanism // ConfigureInitializeExecTrapOnAllProcessors(); break; default: PreactivateRequest->KernelStatus = DEBUGGER_ERROR_COULD_NOT_FIND_PREACTIVATION_TYPE; return STATUS_UNSUCCESSFUL; } PreactivateRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return STATUS_SUCCESS; } /** * @brief routines for the .pagein command * * @param PageinRequest * * @return BOOLEAN */ BOOLEAN DebuggerCommandBringPagein(PDEBUGGER_PAGE_IN_REQUEST PageinRequest) { // // *** Perform the injection here *** // LogInfo("Page-request is received!"); // // Adjust the flags for showing the successful #PF injection // PageinRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL; return TRUE; }