You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

P/Invoke内存分配异常(System.AccessViolationException)排查

问题:X64下C#解析进程权限时内存访问错误

我尝试编译一段C#代码为X64版本,通过LookupPrivilegeName解析进程权限。代码如下:

namespace ConsoleApp2
{
public class Program
{

    //Open Process Token Starts Here

    public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
    public const UInt32 STANDARD_RIGHTS_READ = 0x00020000;
    public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
    public const UInt32 TOKEN_DUPLICATE = 0x0002;
    public const UInt32 TOKEN_IMPERSONATE = 0x0004;
    public const UInt32 TOKEN_QUERY = 0x0008;
    public const UInt32 TOKEN_QUERY_SOURCE = 0x0010;
    public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
    public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;
    public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
    public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
    public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
    public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
                TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
                TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
                TOKEN_ADJUST_SESSIONID);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, ref IntPtr TokenHandle);

    //Open Prcoess Token Ends Here
    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public uint LowPart;
        public uint HighPart;
    }
    public struct TOKEN_PRIVILEGES
    {
        public int PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID_AND_ATTRIBUTES
    {
        public LUID Luid;
        public UInt32 Attributes;
    }
    //Token Privileges



    //GetTokenInformation Starts Here


    enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        TokenIsAppContainer,
        TokenCapabilities,
        TokenAppContainerSid,
        TokenAppContainerNumber,
        TokenUserClaimAttributes,
        TokenDeviceClaimAttributes,
        TokenRestrictedUserClaimAttributes,
        TokenRestrictedDeviceClaimAttributes,
        TokenDeviceGroups,
        TokenRestrictedDeviceGroups,
        TokenSecurityAttributes,
        TokenIsRestricted,
        TokenProcessTrustLevel,
        TokenPrivateNameSpace,
        TokenSingletonAttributes,
        TokenBnoIsolation,
        TokenChildProcessFlags,
        TokenIsLessPrivilegedAppContainer,
        TokenIsSandboxed,
        TokenIsAppSilo,
        MaxTokenInfoClass
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, long TokenInformationLength, ref long ReturnLength);

    //GetTokenInformation Ends Here

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool LookupPrivilegeName(
   string lpSystemName,
   IntPtr lpLuid,
   [param: MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpName,
   ref long cchName);

    public static void Main()
    {
        IntPtr hwnd = Process.GetCurrentProcess().Handle;
        IntPtr TokenHandle = IntPtr.Zero;
        OpenProcessToken(hwnd, TOKEN_READ, ref TokenHandle);

        long ReturnLength = 0;
        GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, 0, ref ReturnLength);

        IntPtr elevationptr = Marshal.AllocHGlobal(64);
        long TokenInformationLength = 64;
        GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, elevationptr, TokenInformationLength, ref TokenInformationLength);
        TOKEN_PRIVILEGES tp = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(elevationptr, typeof(TOKEN_PRIVILEGES));

        IntPtr startingptr = new IntPtr(elevationptr.ToInt64() + sizeof(uint));

        for (int i = 0; i < tp.PrivilegeCount; i++)
        {
            IntPtr tempptr = new IntPtr(startingptr.ToInt64() + i * Marshal.SizeOf(typeof(LUID_AND_ATTRIBUTES)));
            LUID_AND_ATTRIBUTES laa = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(tempptr, typeof(LUID_AND_ATTRIBUTES));
            IntPtr luidptr = Marshal.AllocHGlobal(Marshal.SizeOf(laa.Luid));
            Marshal.StructureToPtr(laa.Luid, luidptr, true);
            StringBuilder sb = new StringBuilder(50);
            long cchname = 50;
            // Console.WriteLine("[1]LUIDPTR: {0}", luidptr);
            LookupPrivilegeName(null, luidptr, sb, ref cchname);
            //Console.WriteLine("[2]cchname: {0}", cchname);
            Console.WriteLine(sb);
            Marshal.FreeHGlobal(luidptr);
         
        }
        Console.Read();
    }

}

虽然tp.PrivilegeCount的值为5,但程序在第三次循环时崩溃。断点调试发现LookupPrivilegeName访问luidptr内存地址时被阻止,报错信息:

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

这明显是内存分配问题,但不确定遗漏了什么。

编辑补充:更新代码后,程序在X64架构上可以运行,但偶尔会崩溃、输出所有权限或无任何输出。


问题根源及修复方案

  1. 硬编码内存大小导致溢出:手动分配64字节内存给TOKEN_PRIVILEGES,但实际所需内存由GetTokenInformation第一次调用返回的ReturnLength决定,固定大小会导致内存溢出破坏后续数据。
  2. API参数类型不匹配:GetTokenInformation和LookupPrivilegeName中的长度参数在Windows API中是DWORD(对应C#的uint),原代码用long可能引发隐式转换问题。
  3. 手动指针偏移易出错:直接计算内存偏移操作指针,容易因结构对齐、类型大小变化导致内存访问错误。
  4. 固定缓冲区长度不足:StringBuilder固定50长度,无法容纳较长的权限名称,导致缓冲区溢出。

修复后的代码

using System;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ConsoleApp2
{
    public class Program
    {
        // Open Process Token Constants
        public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
        public const UInt32 STANDARD_RIGHTS_READ = 0x00020000;
        public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
        public const UInt32 TOKEN_DUPLICATE = 0x0002;
        public const UInt32 TOKEN_IMPERSONATE = 0x0004;
        public const UInt32 TOKEN_QUERY = 0x0008;
        public const UInt32 TOKEN_QUERY_SOURCE = 0x0010;
        public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
        public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;
        public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
        public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
        public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
        public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
                    TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
                    TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
                    TOKEN_ADJUST_SESSIONID);

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, ref IntPtr TokenHandle);

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID
        {
            public uint LowPart;
            public uint HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID_AND_ATTRIBUTES
        {
            public LUID Luid;
            public UInt32 Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_PRIVILEGES
        {
            public int PrivilegeCount;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
            public LUID_AND_ATTRIBUTES[] Privileges;
        }

        enum TOKEN_INFORMATION_CLASS
        {
            TokenUser = 1,
            TokenGroups,
            TokenPrivileges,
            TokenOwner,
            TokenPrimaryGroup,
            TokenDefaultDacl,
            TokenSource,
            TokenType,
            TokenImpersonationLevel,
            TokenStatistics,
            TokenRestrictedSids,
            TokenSessionId,
            TokenGroupsAndPrivileges,
            TokenSessionReference,
            TokenSandBoxInert,
            TokenAuditPolicy,
            TokenOrigin,
            TokenElevationType,
            TokenLinkedToken,
            TokenElevation,
            TokenHasRestrictions,
            TokenAccessInformation,
            TokenVirtualizationAllowed,
            TokenVirtualizationEnabled,
            TokenIntegrityLevel,
            TokenUIAccess,
            TokenMandatoryPolicy,
            TokenLogonSid,
            TokenIsAppContainer,
            TokenCapabilities,
            TokenAppContainerSid,
            TokenAppContainerNumber,
            TokenUserClaimAttributes,
            TokenDeviceClaimAttributes,
            TokenRestrictedUserClaimAttributes,
            TokenRestrictedDeviceClaimAttributes,
            TokenDeviceGroups,
            TokenRestrictedDeviceGroups,
            TokenSecurityAttributes,
            TokenIsRestricted,
            TokenProcessTrustLevel,
            TokenPrivateNameSpace,
            TokenSingletonAttributes,
            TokenBnoIsolation,
            TokenChildProcessFlags,
            TokenIsLessPrivilegedAppContainer,
            TokenIsSandboxed,
            TokenIsAppSilo,
            MaxTokenInfoClass
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, uint TokenInformationLength, ref uint ReturnLength);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool LookupPrivilegeName(
       string lpSystemName,
       ref LUID lpLuid,
       [param: MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpName,
       ref uint cchName);

        public static void Main()
        {
            IntPtr processHandle = Process.GetCurrentProcess().Handle;
            IntPtr tokenHandle = IntPtr.Zero;
            if (!OpenProcessToken(processHandle, TOKEN_READ, ref tokenHandle))
            {
                Console.WriteLine($"OpenProcessToken failed: {Marshal.GetLastWin32Error()}");
                return;
            }

            try
            {
                uint returnLength = 0;
                // 第一次调用获取所需内存大小
                if (!GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, 0, ref returnLength))
                {
                    int error = Marshal.GetLastWin32Error();
                    if (error != 0x0000007A) // ERROR_INSUFFICIENT_BUFFER
                    {
                        Console.WriteLine($"GetTokenInformation failed: {error}");
                        return;
                    }
                }

                // 分配足够的内存
                IntPtr tokenInfoPtr = Marshal.AllocHGlobal((int)returnLength);
                try
                {
                    if (!GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, tokenInfoPtr, returnLength, ref returnLength))
                    {
                        Console.WriteLine($"GetTokenInformation failed: {Marshal.GetLastWin32Error()}");
                        return;
                    }

                    // 反序列化整个TOKEN_PRIVILEGES结构
                    TOKEN_PRIVILEGES tokenPrivileges = Marshal.PtrToStructure<TOKEN_PRIVILEGES>(tokenInfoPtr);

                    for (int i = 0; i < tokenPrivileges.PrivilegeCount; i++)
                    {
                        LUID_AND_ATTRIBUTES luidAttr = tokenPrivileges.Privileges[i];
                        uint nameLength = 0;
                        // 先获取权限名称所需的缓冲区长度
                        if (!LookupPrivilegeName(null, ref luidAttr.Luid, null, ref nameLength))
                        {
                            int error = Marshal.GetLastWin32Error();
                            if (error != 0x0000007A) // ERROR_INSUFFICIENT_BUFFER
                            {
                                Console.WriteLine($"LookupPrivilegeName (get length) failed: {error}");
                                continue;
                            }
                        }

                        StringBuilder privilegeName = new StringBuilder((int)nameLength);
                        if (LookupPrivilegeName(null, ref luidAttr.Luid, privilegeName, ref nameLength))
                        {
                            Console.WriteLine(privilegeName.ToString());
                        }
                        else
                        {
                            Console.WriteLine($"LookupPrivilegeName failed: {Marshal.GetLastWin32Error()}");
                        }
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(tokenInfoPtr);
                }
            }
            finally
            {
                // 关闭令牌句柄
                if (tokenHandle != IntPtr.Zero)
                {
                    CloseHandle(tokenHandle);
                }
            }

            Console.Read();
        }

        // 补充CloseHandle声明,用于释放令牌句柄
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandle(IntPtr hObject);
    }
}

关键修复点说明

  • 匹配API参数类型:将长度参数改为uint,与Windows API的DWORD类型一致。
  • 动态分配内存:根据GetTokenInformation返回的实际需求分配内存,避免溢出。
  • 直接反序列化结构:使用Marshal.PtrToStructure获取完整结构,避免手动指针操作错误。
  • 动态获取缓冲区长度:先调用LookupPrivilegeName获取权限名称所需长度,再创建对应大小的StringBuilder。
  • 资源安全释放:用try/finally确保内存和句柄被正确释放,防止泄漏。

内容的提问来源于stack exchange,提问作者Paul9002

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 18:31:25