/// <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);
            }
        }
        /// <summary>
        /// Extracts Token information from a thread's memory by wrapping GetTokenInformation(). Returns token information specified by the tokenInformationClass param
        /// </summary>
        /// <param name="hToken"></param>
        /// <param name="tokenInformationClass"></param>
        /// <returns>String containing the requested token information</returns>
        static string QueryToken(IntPtr hToken, TOKEN_INFORMATION_CLASS tokenInformationClass)
        {
            int tokenInformationLength = 0;

            // First need to get the length of TokenInformation - won't return true
            bool result = GetTokenInformation(hToken, tokenInformationClass, IntPtr.Zero, tokenInformationLength, out tokenInformationLength);
            // Buffer for the struct
            IntPtr tokenInformation = Marshal.AllocHGlobal(tokenInformationLength);

            // Make call to GetTokenInformation() and get particular Struct
            switch (tokenInformationClass)
            {
            case TOKEN_INFORMATION_CLASS.TokenUser:

                // Store the requested token information in the buffer
                result = GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenUser, tokenInformation, tokenInformationLength, out tokenInformationLength);

                if (result)
                {
                    // Marshal the buffer to TOKEN_USER Struct
                    TOKEN_USER tokenUser = (TOKEN_USER)Marshal.PtrToStructure(tokenInformation, typeof(TOKEN_USER));

                    // Extract SID from the TOKEN_USER struct
                    IntPtr pSID = IntPtr.Zero;
                    ConvertSidToStringSid(tokenUser.User.Sid, out pSID);
                    string SID = Marshal.PtrToStringAuto(pSID);

                    return(SID);
                }
                else
                {
                    return(null);
                }

            case TOKEN_INFORMATION_CLASS.TokenPrivileges:

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

                if (result)
                {
                    TOKEN_PRIVILEGES tokenPrivileges = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(tokenInformation, typeof(TOKEN_PRIVILEGES));

                    StringBuilder stringBuilder = new StringBuilder();

                    for (int i = 0; i < tokenPrivileges.PrivilegeCount; i++)
                    {
                        // Bitwise AND comparison to check that each token privilege attribute for SE_PRIVILEGE_ENABLED
                        if (((LUID_ATTRIBUTES)tokenPrivileges.Privileges[i].Attributes & LUID_ATTRIBUTES.SE_PRIVILEGE_ENABLED) == LUID_ATTRIBUTES.SE_PRIVILEGE_ENABLED)
                        {
                            // Append the privilege to the stringBuilder
                            stringBuilder.Append($", {tokenPrivileges.Privileges[i].Luid.LowPart.ToString()}");
                        }
                    }

                    return(stringBuilder.ToString().Remove(0, 2));
                }
                else
                {
                    return(null);
                }

            case TOKEN_INFORMATION_CLASS.TokenIntegrityLevel:

                // Mandatory Level SIDs for QueryToken()
                // https://support.microsoft.com/en-au/help/243330/well-known-security-identifiers-in-windows-operating-systems#allsids
                Dictionary <string, string> tokenIntegritySIDs = new Dictionary <string, string>
                {
                    { "S-1-16-0", "Untrusted Mandatory Level" },
                    { "S-1-16-4096", "Low Mandatory Level" },
                    { "S-1-16-8192", "Medium Mandatory Level" },
                    { "S-1-16-8448", "Medium Plus Mandatory Level" },
                    { "S-1-16-12288", "High Mandatory Level" },
                    { "S-1-16-16384", "System Mandatory Level" },
                    { "S-1-16-20480", "Protected Process Mandatory Level" },
                    { "S-1-16-28672", "Secure Process Mandatory Level" }
                };

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

                if (result)
                {
                    TOKEN_MANDATORY_LABEL tokenMandatoryLabel = (TOKEN_MANDATORY_LABEL)Marshal.PtrToStructure(tokenInformation, typeof(TOKEN_MANDATORY_LABEL));

                    // Extract SID string from TOKEN_MANDATORY_LABEL
                    IntPtr pSID = IntPtr.Zero;
                    ConvertSidToStringSid(tokenMandatoryLabel.label.Sid, out pSID);
                    string SID = Marshal.PtrToStringAuto(pSID);

                    if (tokenIntegritySIDs.ContainsKey(SID))
                    {
                        return(tokenIntegritySIDs[SID]);
                    }
                    else
                    {
                        return(null);
                    }
                }
                else
                {
                    return(null);
                }

            case TOKEN_INFORMATION_CLASS.TokenOrigin:

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

                if (result)
                {
                    TOKEN_ORIGIN tokenOrigin = (TOKEN_ORIGIN)Marshal.PtrToStructure(tokenInformation, typeof(TOKEN_ORIGIN));
                    string       logonId     = tokenOrigin.OriginatingLogonSession.LowPart.ToString();
                    return(logonId);
                }
                else
                {
                    return(null);
                }
            }
            return(null);
        }