diff --git a/Models/MemoryRegion.cs b/Models/MemoryRegion.cs index 05a60a4..2a01a24 100644 --- a/Models/MemoryRegion.cs +++ b/Models/MemoryRegion.cs @@ -22,7 +22,7 @@ namespace WebmrAPI.Models } public string? BaseAddress { - get => (MemoryAddress > 0) ? $"0x{MemoryAddress:X12}" : null; + get => (MemoryAddress > 0) ? $"0x{MemoryAddress:X16}" : null; } } } diff --git a/Models/MemoryRegionInfo.cs b/Models/MemoryRegionInfo.cs index b012347..733b8f2 100644 --- a/Models/MemoryRegionInfo.cs +++ b/Models/MemoryRegionInfo.cs @@ -1,24 +1,26 @@ // File: Models/MemoryRegionInfo.cs +using WebmrAPI.Services; + namespace WebmrAPI.Models { public class MemoryRegionInfo : MemoryRegion { - private string _state = String.Empty; - private string _protect = String.Empty; - private string _type = String.Empty; + private WinApi.MemoryState _state = 0; + private WinApi.MemoryPageProtectionState _protect = 0; + private WinApi.MemoryType _type = 0; - public string State + public WinApi.MemoryState MemoryState { get => LockedGet(ref _state); set => LockedSet(ref _state, value); } - public string Protect + public WinApi.MemoryPageProtectionState MemoryPageProtection { get => LockedGet(ref _protect); set => LockedSet(ref _protect, value); } - public string Type + public WinApi.MemoryType MemoryType { get => LockedGet(ref _type); set => LockedSet(ref _type, value); diff --git a/Models/ProcessBaseInfo.cs b/Models/ProcessBaseInfo.cs index 5e4d8c2..f624fe4 100644 --- a/Models/ProcessBaseInfo.cs +++ b/Models/ProcessBaseInfo.cs @@ -34,7 +34,7 @@ namespace WebmrAPI.Models [JsonIgnore] public double ProcessorTime { - get { lock (_lock) return (_lastPTime - _curPTime).TotalMilliseconds; } + get { lock (_lock) return (_curPTime - _lastPTime).TotalMilliseconds; } } public int PID { diff --git a/Services/ProcessMonitor.cs b/Services/ProcessMonitor.cs index 59d72e4..6335ba1 100644 --- a/Services/ProcessMonitor.cs +++ b/Services/ProcessMonitor.cs @@ -128,15 +128,15 @@ namespace WebmrAPI.Services break; } - if (mbi.State == WinApi.MEM_COMMIT || mbi.State == WinApi.MEM_RESERVE) + if (mbi.State == WinApi.MemoryState.Commit || mbi.State == WinApi.MemoryState.Reserve) { regions.Add(new MemoryRegionInfo { MemoryAddress = mbi.BaseAddress.ToInt64(), MemorySize = mbi.RegionSize.ToUInt64(), - State = WinApi.GetStateString(mbi.State), - Protect = WinApi.GetProtectString(mbi.Protect), - Type = WinApi.GetTypeString(mbi.Type) + MemoryState = mbi.State, + MemoryPageProtection = mbi.PageProtection, + MemoryType = mbi.Type }); } @@ -153,7 +153,7 @@ namespace WebmrAPI.Services private void PopulateBaseProcessInfo(ProcessInfo dst, System.Diagnostics.Process process, Dictionary wmiData) { dst.PID = process.Id; - dst.MemorySize = (ulong)process.VirtualMemorySize64; + dst.MemorySize = (ulong)process.WorkingSet64; dst.ThreadCount = process.Threads.Count; dst.TotalProcessorTime = process.TotalProcessorTime; dst.MemoryAddress = 0x000000000000; diff --git a/Services/WinApi.cs b/Services/WinApi.cs index d82b9aa..48830b1 100644 --- a/Services/WinApi.cs +++ b/Services/WinApi.cs @@ -1,60 +1,58 @@ // File: Services/WinApi.cs using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text.Json.Serialization; +using WebmrAPI.Utils; namespace WebmrAPI.Services { + [SupportedOSPlatform("windows")] public static class WinApi { public const uint PROCESS_QUERY_INFORMATION = 0x00000400; public const uint PROCESS_VM_READ = 0x00000010; public const uint PROCESS_VM_OPERATION = 0x00000008; - public const uint MEM_COMMIT = 0x00001000; - public const uint MEM_FREE = 0x00010000; - public const uint MEM_RESERVE = 0x00002000; - - public const uint MEM_IMAGE = 0x01000000; - public const uint MEM_MAPPED = 0x00040000; - public const uint MEM_PRIVATE = 0x00020000; - public static int LastError { get => Marshal.GetLastWin32Error(); } public static uint MBISize { get => (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)); } - private static string GetHexValue(uint data) + [JsonConverter(typeof(JsonEnumConverter))] + public enum MemoryState : uint { - return $"0x{data:X8}"; + Undefined = 0, + Commit = 0x00001000, + Reserve = 0x00002000, + Free = 0x00010000, } - public static string GetStateString(uint state) + [JsonConverter(typeof(JsonEnumConverter))] + public enum MemoryType : uint { - switch (state) - { - case MEM_COMMIT: return "MEM_COMMIT"; - case MEM_FREE: return "MEM_FREE"; - case MEM_RESERVE: return "MEM_RESERVE"; - default: return GetHexValue(state); - } + Undefined = 0, + Image = 0x01000000, + Mapped = 0x00040000, + Private = 0x00020000, } - public static string GetProtectString(uint protect) + [JsonConverter(typeof(JsonEnumConverter))] + public enum MemoryPageProtectionState : uint { - switch (protect) - { - default: return GetHexValue(protect); - } + Undefined = 0, + NoAccess = 0x0001, + ReadOnly = 0x0002, + ReadWrite = 0x0004, + WriteCopy = 0x0008, + Execute = 0x0010, + ExecuteRead = 0x0020, + ExecuteReadWrite = 0x0040, + ExecuteWriteCopy = 0x0080, + Guard = 0x0100, + NoCache = 0x0200, + WriteCombine = 0x0400, } - public static string GetTypeString(uint type) - { - switch (type) - { - case MEM_IMAGE: return "MEM_IMAGE"; - case MEM_MAPPED: return "MEM_MAPPED"; - case MEM_PRIVATE: return "MEM_PRIVATE"; - default: return GetHexValue(type); - } - } + // --- P/Invoke Declarations --- // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information [StructLayout(LayoutKind.Sequential)] @@ -65,9 +63,9 @@ namespace WebmrAPI.Services public uint AllocationProtect; public ushort PartitionId; public UIntPtr RegionSize; - public uint State; - public uint Protect; - public uint Type; + public MemoryState State; + public MemoryPageProtectionState PageProtection; + public MemoryType Type; } @@ -92,5 +90,162 @@ namespace WebmrAPI.Services out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength ); + + // --- Module-related P/Invokes (from psapi.dll and kernel32.dll) --- + + // https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex + [DllImport("psapi.dll", SetLastError = true)] + public static extern bool EnumProcessModulesEx( + IntPtr hProcess, + [Out] IntPtr[] lphModule, // Array to receive module handles + uint cb, // Size of the lphModule array, in bytes + out uint lpcbNeeded, // Number of bytes required to store all module handles + uint dwFilterFlag // Filter for modules (e.g., LIST_MODULES_ALL) + ); + + // https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulefilenameexw + [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint GetModuleFileNameEx( + IntPtr hProcess, + IntPtr hModule, + [Out] System.Text.StringBuilder lpFilename, + uint nSize + ); + + // https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamew + [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint GetModuleBaseName( + IntPtr hProcess, + IntPtr hModule, + [Out] System.Text.StringBuilder lpBaseName, + uint nSize + ); + + // https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-_moduleinfo + [StructLayout(LayoutKind.Sequential)] + public struct MODULEINFO + { + public IntPtr lpBaseOfDll; + public uint SizeOfImage; + public IntPtr EntryPoint; + } + + // https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmoduleinformation + [DllImport("psapi.dll", SetLastError = true)] + public static extern bool GetModuleInformation( + IntPtr hProcess, + IntPtr hModule, + out MODULEINFO lpmodinfo, + uint cb + ); + + // --- Thread-related P/Invokes (from kernel32.dll and ntdll.dll) --- + + // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenThread( + uint dwDesiredAccess, + [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, + uint dwThreadId + ); + + + // https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationthread + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtQueryInformationThread( + IntPtr ThreadHandle, + THREAD_INFO_CLASS ThreadInformationClass, + IntPtr ThreadInformation, // Pointer to buffer + uint ThreadInformationLength, + out uint ReturnLength + ); + + // Enum for ThreadInformationClass in NtQueryInformationThread + // https://ntdoc.m417z.com/threadinfoclass + public enum THREAD_INFO_CLASS : int + { + ThreadBasicInformation, // q: THREAD_BASIC_INFORMATION + ThreadTimes, // q: KERNEL_USER_TIMES + ThreadPriority, // s: KPRIORITY (requires SeIncreaseBasePriorityPrivilege) + ThreadBasePriority, // s: KPRIORITY + ThreadAffinityMask, // s: KAFFINITY + ThreadImpersonationToken, // s: HANDLE + ThreadDescriptorTableEntry, // q: DESCRIPTOR_TABLE_ENTRY (or WOW64_DESCRIPTOR_TABLE_ENTRY) + ThreadEnableAlignmentFaultFixup, // s: BOOLEAN + ThreadEventPair, // Obsolete + ThreadQuerySetWin32StartAddress, // qs: PVOID (requires THREAD_Set_LIMITED_INFORMATION) + ThreadZeroTlsCell, // s: ULONG // TlsIndex // 10 + ThreadPerformanceCount, // q: LARGE_INTEGER + ThreadAmILastThread, // q: ULONG + ThreadIdealProcessor, // s: ULONG + ThreadPriorityBoost, // qs: ULONG + ThreadSetTlsArrayAddress, // s: ULONG_PTR + ThreadIsIoPending, // q: ULONG + ThreadHideFromDebugger, // q: BOOLEAN; s: void + ThreadBreakOnTermination, // qs: ULONG + ThreadSwitchLegacyState, // s: void // NtCurrentThread // NPX/FPU + ThreadIsTerminated, // q: ULONG // 20 + ThreadLastSystemCall, // q: THREAD_LAST_SYSCALL_INFORMATION + ThreadIoPriority, // qs: IO_PRIORITY_HINT (requires SeIncreaseBasePriorityPrivilege) + ThreadCycleTime, // q: THREAD_CYCLE_TIME_INFORMATION (requires THREAD_QUERY_LIMITED_INFORMATION) + ThreadPagePriority, // qs: PAGE_PRIORITY_INFORMATION + ThreadActualBasePriority, // s: LONG (requires SeIncreaseBasePriorityPrivilege) + ThreadTebInformation, // q: THREAD_TEB_INFORMATION (requires THREAD_GET_CONTEXT + THREAD_SET_CONTEXT) + ThreadCSwitchMon, // Obsolete + ThreadCSwitchPmu, // Obsolete + ThreadWow64Context, // qs: WOW64_CONTEXT, ARM_NT_CONTEXT since 20H1 + ThreadGroupInformation, // qs: GROUP_AFFINITY // 30 + ThreadUmsInformation, // q: THREAD_UMS_INFORMATION // Obsolete + ThreadCounterProfiling, // q: BOOLEAN; s: THREAD_PROFILING_INFORMATION? + ThreadIdealProcessorEx, // qs: PROCESSOR_NUMBER; s: previous PROCESSOR_NUMBER on return + ThreadCpuAccountingInformation, // q: BOOLEAN; s: HANDLE (NtOpenSession) // NtCurrentThread // since WIN8 + ThreadSuspendCount, // q: ULONG // since WINBLUE + ThreadHeterogeneousCpuPolicy, // q: KHETERO_CPU_POLICY // since THRESHOLD + ThreadContainerId, // q: GUID + ThreadNameInformation, // qs: THREAD_NAME_INFORMATION (requires THREAD_SET_LIMITED_INFORMATION) + ThreadSelectedCpuSets, // q: ULONG[] + ThreadSystemThreadInformation, // q: SYSTEM_THREAD_INFORMATION // 40 + ThreadActualGroupAffinity, // q: GROUP_AFFINITY // since THRESHOLD2 + ThreadDynamicCodePolicyInfo, // q: ULONG; s: ULONG (NtCurrentThread) + ThreadExplicitCaseSensitivity, // qs: ULONG; s: 0 disables, otherwise enables // (requires SeDebugPrivilege and PsProtectedSignerAntimalware) + ThreadWorkOnBehalfTicket, // ALPC_WORK_ON_BEHALF_TICKET // RTL_WORK_ON_BEHALF_TICKET_EX // NtCurrentThread + ThreadSubsystemInformation, // q: SUBSYSTEM_INFORMATION_TYPE // since REDSTONE2 + ThreadDbgkWerReportActive, // s: ULONG; s: 0 disables, otherwise enables + ThreadAttachContainer, // s: HANDLE (job object) // NtCurrentThread + ThreadManageWritesToExecutableMemory, // MANAGE_WRITES_TO_EXECUTABLE_MEMORY // since REDSTONE3 + ThreadPowerThrottlingState, // qs: POWER_THROTTLING_THREAD_STATE // since REDSTONE3 (set), WIN11 22H2 (query) + ThreadWorkloadClass, // THREAD_WORKLOAD_CLASS // since REDSTONE5 // 50 + ThreadCreateStateChange, // since WIN11 + ThreadApplyStateChange, + ThreadStrongerBadHandleChecks, // s: ULONG // NtCurrentThread // since 22H1 + ThreadEffectiveIoPriority, // q: IO_PRIORITY_HINT + ThreadEffectivePagePriority, // q: ULONG + ThreadUpdateLockOwnership, // THREAD_LOCK_OWNERSHIP // since 24H2 + ThreadSchedulerSharedDataSlot, // SCHEDULER_SHARED_DATA_SLOT_INFORMATION + ThreadTebInformationAtomic, // q: THREAD_TEB_INFORMATION (requires THREAD_GET_CONTEXT + THREAD_QUERY_INFORMATION) + ThreadIndexInformation, // THREAD_INDEX_INFORMATION + MaxThreadInfoClass + } + + // Structure for ThreadBasicInformation (if needed, though ThreadQuerySetWin32StartAddress directly gives address) + // https://ntdoc.m417z.com/thread_basic_information + [StructLayout(LayoutKind.Sequential)] + public struct THREAD_BASIC_INFORMATION + { + public int ExitStatus; + public IntPtr TebBaseAddress; + public CLIENT_ID ClientId; + public IntPtr AffinityMask; + public int Priority; + public int BasePriority; + } + + // https://ntdoc.m417z.com/client_id + [StructLayout(LayoutKind.Sequential)] + public struct CLIENT_ID + { + public IntPtr UniqueProcess; + public IntPtr UniqueThread; + } } } diff --git a/Utils/JsonEnumConverter.cs b/Utils/JsonEnumConverter.cs new file mode 100644 index 0000000..37a0aa7 --- /dev/null +++ b/Utils/JsonEnumConverter.cs @@ -0,0 +1,54 @@ +// File: Utils/JsonEnumConverter.cs + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace WebmrAPI.Utils +{ + public class JsonEnumConverter : JsonConverter where T : Enum + { + private T GetDefaultValue(Type type) + { + if (Enum.TryParse(type, "Undefined", true, out object? result) && result != null) + { + return (T)result; + } + return (T)Enum.ToObject(type, 0); + } + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + if (Enum.TryParse(typeToConvert, reader.GetString(), true, out object? result) && result != null) + { + return (T)result; + } + } + else if (reader.TokenType == JsonTokenType.Number) + { + if (reader.TryGetInt32(out int intValue)) + { + T status = (T)Enum.ToObject(typeToConvert, intValue); + if (Enum.IsDefined(typeof(T), status)) + { + return status; + } + } + } + return GetDefaultValue(typeToConvert); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (Enum.IsDefined(typeof(T), value)) + { + writer.WriteStringValue(value.ToString()); + } + else + { + writer.WriteStringValue("Undefined"); + } + } + } +}