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架构上可以运行,但偶尔会崩溃、输出所有权限或无任何输出。
问题根源及修复方案
- 硬编码内存大小导致溢出:手动分配64字节内存给
TOKEN_PRIVILEGES,但实际所需内存由GetTokenInformation第一次调用返回的ReturnLength决定,固定大小会导致内存溢出破坏后续数据。 - API参数类型不匹配:
GetTokenInformation和LookupPrivilegeName中的长度参数在Windows API中是DWORD(对应C#的uint),原代码用long可能引发隐式转换问题。 - 手动指针偏移易出错:直接计算内存偏移操作指针,容易因结构对齐、类型大小变化导致内存访问错误。
- 固定缓冲区长度不足:
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
相关产品推荐
相关产品推荐

