Search code examples
c#dllimportadvapi32

RegEnumKeyEx - Access violation writing location


The situation:

  • Need to fetch a list of all subkeys of a particular registry key.
  • Need to access both 32bit and 64bit software keys, so I cannot use the Registry namespace.
  • Using CSharp in .Net 3.5, and registry functionality from advapi32.dll

I have most of the functionality working but I'm stuck on an error. When it reaches a key that contains values, it will either skip it or throw the following error:

  • "Unhandled exception at 0x00C819CD in xxxxx.exe: 0xC0000005: Access violation writing location 0x00720077."

If the error occurs, it does not land in either of my catch statements. It hard crashes the program. From what I've read on the forums, I believe it may be an issue with it writing to protected memory but all of the examples I see are for C++

My Declaration (from P/Invoke Interop Assistant):

    [DllImportAttribute("advapi32.dll", EntryPoint = "RegEnumKeyExW")]
    public static extern int RegEnumKeyExW(
        [InAttribute()] IntPtr hKey, 
        uint dwIndex, 
        [OutAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder lpName, 
        ref uint lpcchName, 
        IntPtr lpReserved, 
        IntPtr lpClass, 
        IntPtr lpcchClass, 
        IntPtr lpftLastWriteTime);

My Function (obviously a work in progress so it's a bit messy):

    static private List<string> GetSubKeys(UIntPtr inHive, String inKeyName, RegSAM in32or64key) {
        int hkey = 0;
        uint dwIndex = 0;
        long enumStatus = 0;

        List<string> keys = new List<string>();
        try {
            uint lResult = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                inKeyName,
                0,
                (int)RegSAM.QueryValue | (int)RegSAM.EnumerateSubKeys | (int)in32or64key,
                out hkey);

            if (lResult == 0) {

                while (enumStatus == ERROR_SUCCESS) {
                    StringBuilder lpName = new StringBuilder();
                    uint lpcchName = 256;
                    IntPtr lpReserved = IntPtr.Zero;
                    IntPtr lpClass = IntPtr.Zero;
                    IntPtr lpcchClass = IntPtr.Zero;
                    IntPtr lpftLastWriteTime = IntPtr.Zero;

                    enumStatus = RegEnumKeyExW(
                        (IntPtr)hkey,
                        dwIndex,
                        lpName,
                        ref lpcchName,
                        lpReserved,
                        lpClass,
                        lpcchClass,
                        lpftLastWriteTime);

                    switch (enumStatus) {
                        case ERROR_SUCCESS:
                            Console.WriteLine(string.Format("Key Found: {0}", lpName.ToString()));
                            break;
                        case ERROR_NO_MORE_ITEMS:
                            break;
                        default:
                            string error = new System.ComponentModel.Win32Exception((int)enumStatus).Message;
                            Console.WriteLine(string.Format("RegEnumKeyEx Error: {0}", error));

                            break;
                    }
                    dwIndex++;
                }
            } else {
                Console.WriteLine(string.Format("RegOpenKey Error: {0}", lResult));
            }
        } catch (System.Runtime.InteropServices.COMException ex) {
            Console.WriteLine(string.Format("COM Error: {0}", ex.Message));
        } catch (Exception ex) {
            Console.WriteLine(string.Format("Managed Error: {0}", ex.Message));
        } finally {
            if (0 != hkey) RegCloseKey(hkey);
        }
        return keys;
    }

    #endregion

Solution

  •     StringBuilder lpName = new StringBuilder();
        uint lpcchName = 256;
    

    You are lying about the StringBuilder's capacity. It is 0, not 256. This will cause the pinvoke call to corrupt the GC heap. This eventually causes a hard crash, typically when a garbage collection takes place. Fix:

        uint lpcchName = 256;
        StringBuilder lpName = new StringBuilder(lpcchName);
    

    Using the .NET RegistryKey.GetSubKeyNames() method instead would probably be wise.