/// <summary>
        /// Get SECURITY_LOGON_SESSION_DATA for a process or thread via a handle to its token and populate an InjectedThread object's Logon Session values
        /// </summary>
        /// <param name="hToken"></param>
        /// <param name="injectedThread"></param>
        static void GetLogonSessionData(IntPtr hToken, InjectedThread injectedThread)
        {
            int    tokenInformationLength = 0;
            bool   result           = GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenOrigin, IntPtr.Zero, tokenInformationLength, out tokenInformationLength);
            IntPtr tokenInformation = Marshal.AllocHGlobal(tokenInformationLength);

            result = GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenOrigin, tokenInformation, tokenInformationLength, out tokenInformationLength);

            if (result)
            {
                // GetTokenInformation to retreive LUID struct
                TOKEN_ORIGIN tokenOrigin = (TOKEN_ORIGIN)Marshal.PtrToStructure(tokenInformation, typeof(TOKEN_ORIGIN));
                IntPtr       pLUID       = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LUID)));

                // Get pointer to LUID struct for LsaGetLogonSessionData
                Marshal.StructureToPtr(tokenOrigin.OriginatingLogonSession, pLUID, false);

                IntPtr pLogonSessionData = IntPtr.Zero;
                LsaGetLogonSessionData(pLUID, out pLogonSessionData);

                SECURITY_LOGON_SESSION_DATA logonSessionData = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(pLogonSessionData, typeof(SECURITY_LOGON_SESSION_DATA));

                // Check for a valid logon
                if (logonSessionData.PSiD != IntPtr.Zero)
                {
                    if (injectedThread.Username.Equals("NO OWNER"))
                    {
                        string domain   = Marshal.PtrToStringUni(logonSessionData.LoginDomain.buffer).Trim();
                        string username = Marshal.PtrToStringUni(logonSessionData.Username.buffer).Trim();
                        injectedThread.Username = $"{domain}\\{username}";
                    }

                    // Add logon session information to InjectedThread object
                    injectedThread.LogonSessionStartTime = DateTime.FromFileTime(logonSessionData.LoginTime);
                    injectedThread.LogonType             = Enum.GetName(typeof(SECURITY_LOGON_TYPES), logonSessionData.LogonType);
                    injectedThread.AuthenticationPackage = Marshal.PtrToStringAuto(logonSessionData.AuthenticationPackage.buffer);
                }

                LsaFreeReturnBuffer(pLogonSessionData);
            }
        }
        public static List <InjectedThread> InjectedThreads()
        {
            // Check if running as administrator first? Or at least check if SeDebugPrivilege enabled?
            if (IsUserAnAdmin() == false)
            {
                Console.WriteLine("Program is not running as Administrator. Exiting...");
                System.Environment.Exit(1);
            }

            List <InjectedThread> injectedThreads = new List <InjectedThread>();

            // Create array of Process objects for each running process
            Process[] runningProcesses = Process.GetProcesses();

            // Iterate over each process and get all threads by ID
            foreach (Process process in runningProcesses)
            {
                // PID 0 and PID 4 aren't valid targets for injection
                if (process.Id != 0 && process.Id != 4)
                {
                    IntPtr hProcess;

                    try
                    {
                        // Get handle to the process
                        hProcess = OpenProcess(ProcessAccessFlags.All, false, process.Id);
                    }
                    catch (System.ComponentModel.Win32Exception)
                    {
                        Console.WriteLine($"Couldn't get handle to process: {process.Id} - System.ComponentModel.Win32Exception - Access Is Denied");
                        continue;
                    }
                    catch (System.InvalidOperationException)
                    {
                        Console.WriteLine($"Couldn't get handle to process {process.Id} - System.InvalidOperationException - Process has Exited");
                        continue;
                    }

                    // Get all threads under running process
                    ProcessThreadCollection threadCollection = process.Threads;

                    // Iterate over each thread under the process
                    foreach (ProcessThread thread in threadCollection)
                    {
                        // Get handle to the thread
                        IntPtr hThread = OpenThread(ThreadAccess.AllAccess, false, thread.Id);

                        // Create buffer to store pointer to the thread's base address - NTQueryInformationThread writes to this buffer
                        IntPtr buf = Marshal.AllocHGlobal(IntPtr.Size);

                        // Retrieve thread's Win32StartAddress - Different to thread.StartAddress
                        Int32 result = NtQueryInformationThread(hThread, ThreadInfoClass.ThreadQuerySetWin32StartAddress, buf, IntPtr.Size, IntPtr.Zero);

                        if (result == 0)
                        {
                            // Need to Marshal Win32 type pointer from CLR type IntPtr to access the thread's base address via pointer
                            IntPtr threadBaseAddress = Marshal.ReadIntPtr(buf);

                            // Retrieve MEMORY_BASIC_INFORMATION struct for each thread - assumes 64bit processes, otherwise need to use MEMORY_BASIC_INFORMATION32
                            MEMORY_BASIC_INFORMATION64 memBasicInfo = new MEMORY_BASIC_INFORMATION64();
                            VirtualQueryEx(hProcess, threadBaseAddress, out memBasicInfo, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION64)));

                            // Check the State and Type fields for the thread's MEMORY_BASIC_INFORMATION
                            // Resolve to false suggests code running from this thread does not have a corresponding image file on disk, likely code injection
                            if (memBasicInfo.State == MemoryBasicInformationState.MEM_COMMIT && memBasicInfo.Type != MemoryBasicInformationType.MEM_IMAGE)
                            {
                                // Create new InjectedThread object and set initial variables
                                InjectedThread injectedThread = new InjectedThread()
                                {
                                    ProcessName               = process.ProcessName,
                                    ProcessID                 = process.Id,
                                    ThreadId                  = thread.Id,
                                    BaseAddress               = threadBaseAddress,
                                    Path                      = process.MainModule.FileName,
                                    Size                      = (int)memBasicInfo.RegionSize,
                                    CommandLine               = GetProcessCommandLine(process),
                                    MemoryState               = Enum.GetName(typeof(MemoryBasicInformationState), memBasicInfo.State),
                                    MemoryType                = Enum.GetName(typeof(MemoryBasicInformationType), memBasicInfo.Type),
                                    MemoryProtection          = Enum.GetName(typeof(MemoryBasicInformationProtection), memBasicInfo.Protect),
                                    AllocatedMemoryProtection = Enum.GetName(typeof(MemoryBasicInformationProtection), memBasicInfo.AllocationProtect),
                                    BasePriority              = thread.BasePriority,
                                    ThreadStartTime           = thread.StartTime
                                };

                                // Get handle to thread token. If Impersonation is not being used, thread will use Process access token
                                // Try OpenThreadToken() - if it fails, use OpenProcessToken()
                                if (OpenThreadToken(hThread, TokenAccessFlags.TOKEN_QUERY, false, out IntPtr hToken) == false)
                                {
                                    // Thread doesn't have a unique token
                                    injectedThread.IsUniqueThreadToken = false;

                                    // Open process token instead
                                    if (OpenProcessToken(hProcess, TokenAccessFlags.TOKEN_QUERY, out hToken) == false)
                                    {
                                        Console.WriteLine($"Error opening thread and process token: {Marshal.GetLastWin32Error()}\nProcess ID {process.Id}");
                                    }
                                }
                                else
                                {
                                    injectedThread.IsUniqueThreadToken = true;
                                }

                                // Query process or thread token information
                                injectedThread.SecurityIdentifier = QueryToken(hToken, TOKEN_INFORMATION_CLASS.TokenUser);
                                injectedThread.Privileges         = QueryToken(hToken, TOKEN_INFORMATION_CLASS.TokenPrivileges);
                                injectedThread.Integrity          = QueryToken(hToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel);
                                injectedThread.LogonId            = QueryToken(hToken, TOKEN_INFORMATION_CLASS.TokenOrigin);
                                injectedThread.Username           = GetProcessOwner(process.Id);

                                // Get logon session information and add it to the InjectedThread object
                                if (!string.IsNullOrEmpty(injectedThread.LogonId))
                                {
                                    GetLogonSessionData(hToken, injectedThread);
                                }

                                // Get thread's allocated memory via ReadProcessMemory
                                injectedThread.ThreadBytes = GetThreadMemoryBytes(hProcess, threadBaseAddress, injectedThread.Size);

                                // Read the full process memory ;
                                injectedThread.ProcessBytes = GetProcessMemoryBytes(hProcess);

                                // Read full name of executable image for the process
                                int           capacity      = 1024;
                                StringBuilder stringBuilder = new StringBuilder(capacity);
                                QueryFullProcessImageName(hProcess, 0, stringBuilder, ref capacity);
                                injectedThread.KernelPath = stringBuilder.ToString(0, capacity);

                                // Check whether the kernel image path matches Process.MainModule.Filename
                                if (injectedThread.Path.ToLower() != injectedThread.KernelPath.ToLower())
                                {
                                    injectedThread.PathMismatch = true;
                                }

                                injectedThreads.Add(injectedThread);
                                CloseHandle(hToken);
                            }

                            CloseHandle(hThread);
                        }
                    }

                    CloseHandle(hProcess);
                }
            }

            return(injectedThreads);
        }