Search code examples
c#pinvokeaccess-violation

P/Invoke offreg.dll functions - Access violation on subsequent calls


it is my first question here so if I violate some written or unwritten rules, please bear with me.

I am trying to P/Invoke functions from the offreg.dll in C#. To get a feel of how they work, I wrote this very basic console application:

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

namespace ConsoleApp2
{

    class Program
    {
        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "ORGetVersion", CharSet = CharSet.Unicode)]
        public static extern void ORGetVersion(out uint MajorVersion, out uint MinorVersion);

        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "OROpenHive", CharSet = CharSet.Unicode)]
        public static extern int OROpenHive(string HivePath, out IntPtr rootKeyHandle);

        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "OROpenKey", CharSet = CharSet.Unicode)]
        public static extern int OROpenKey(IntPtr KeyHandle, string SubKeyName, out IntPtr SubKeyHandle);

        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "ORCloseKey", CharSet = CharSet.Unicode)]
        public static extern int ORCloseKey(IntPtr KeyHandle);

        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "OREnumKey", CharSet = CharSet.Unicode)]
        public static extern int OREnumKey( IntPtr KeyHandle, 
                                            uint Index, 
                                            [MarshalAs(UnmanagedType.LPWStr)]
                                            StringBuilder SubKeyName, 
                                            ref uint SubKeyLen, 
                                            [MarshalAs(UnmanagedType.LPWStr)]
                                            StringBuilder ClassName, 
                                            ref uint ClassLen, 
                                            out uint LastWriteTime
            );

        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "ORQueryInfoKey", CharSet = CharSet.Unicode)]
        public static extern int ORQueryInfoKey(IntPtr KeyHandle, 
                                            [MarshalAs(UnmanagedType.LPWStr)]
                                            StringBuilder ClassName, 
                                            ref uint ClassLen, 
                                            out uint NumKeys, 
                                            out uint MaxKeyLen, 
                                            out uint NumVals, 
                                            out uint MaxValLen, 
                                            out IntPtr SecDesc, 
                                            out uint LastWrite
            );

        [DllImport("C:\\TEMP\\offreg-x64.dll", EntryPoint = "ORCloseHive", CharSet = CharSet.Unicode)]
        public static extern int ORCloseHive(IntPtr rootKeyHandle);

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            int res = 0;    // result of every OR function call
            IntPtr h;       // hive (or root key) handle


            // Get DLL version
            uint ver1 = 0;
            uint ver2 = 0;
            Program.ORGetVersion(out ver1, out ver2);
            Console.WriteLine("Library Version: " + ver1.ToString() + "." + ver2.ToString());
            // end get version

            // Open hive
            res = Program.OROpenHive(@"C:\TEMP\NTUSER.DAT", out h);
            Console.WriteLine("Open Result: " + res.ToString());
            Console.WriteLine("Open Handle: " + h.ToString());
            // end oppen hive

            // prepare variables for key operations
            StringBuilder ClassName = new StringBuilder(256);
            uint ClassLen = 256;
            StringBuilder SubKeyName = new StringBuilder(256);
            uint SubKeyLen = 256;
            uint NKeys = 0;
            uint maxKeyLen = 0;
            uint NVals = 0;
            uint maxValLen = 0;
            IntPtr SecDesc = IntPtr.Zero;
            uint LastWrite = 0;
            uint SubKeyIndex = 0;   // we are asking for the first subkey
            // end prepare variables

            // Query root key information
            res = Program.ORQueryInfoKey(h, ClassName, ref ClassLen, out NKeys, out maxKeyLen, out NVals, out maxValLen, out SecDesc, out LastWrite);
            Console.WriteLine("Query Result: " + res.ToString());
            Console.WriteLine("Query Class Name: " + ClassName.ToString());
            Console.WriteLine("Query Class Length: " + ClassLen.ToString());
            Console.WriteLine("Query Num Subkeys: " + NKeys.ToString());
            Console.WriteLine("Query Max Subkey Len: " + maxKeyLen.ToString());
            Console.WriteLine("Query Num Values: " + NVals.ToString());
            Console.WriteLine("Query Max Value Len: " + maxValLen.ToString());
            Console.WriteLine("Query Last Write: " + LastWrite.ToString());
            // end query root key information

            // enum first subkey
            res = Program.OREnumKey(h, SubKeyIndex, SubKeyName, ref SubKeyLen, ClassName, ref ClassLen, out LastWrite);
            Console.WriteLine("Enum Result: " + res.ToString());
            Console.WriteLine("Enum Builder: " + SubKeyName.ToString());
            Console.WriteLine("Enum Length: " + SubKeyLen.ToString());
            Console.WriteLine("Enum Capacity: " + SubKeyName.Capacity.ToString());
            // end enum first subkey

            // close hive
            res = Program.ORCloseHive(h);
            Console.WriteLine("Close Result: " + res.ToString());
            // end close hive

            // sayonara
            Console.WriteLine("Press [ENTER] to exit");
            Console.ReadLine();
        }
    }
}

When I run this code as shown above, it will run the ORQueryInfoKey block but give me an Access Violation on attempt to call OREnumKey.

If I comment out the call to ORQueryInfoKey, it runs OK and returns correct info on the first subkey (but I obviously don't get the info about the number of subkeys and values, or their length).

If I comment out the call to OREnumKey, the Access Violation still happens on ORCloseHive.

If I swap the ORQueryInfoKey block and the OREnumKey block, it doesn't throw any exception but the Query call returns 234 (MORE_DATA_AVAILABLE) which means the ClassLen is too short.

If I swap the ORQueryInfoKey block and the OREnumKey block and set ClassLen to 1 before the second call, it runs OK, returns correct data but throws an Access Violation on ORCloseHive.

So to summarize this, after a successfull call to ORQueryInfoKey it appears that the next call using the handle h will produce an Access Violation.

What am I missing?

Any insight is greatly appreciated!


Solution

  • The ORQueryInfoKey was missing a lot of parameters, FILETIME is equivalent to a .NET long (64-bit, and DateTime has a built-in function to convert it), and you must call the API with strings twice: first you pass a null pointer and the API will give you the length, then you allocate the length, call the API again (and convert the allocated pointer back to a string, free the buffer), something like this (I've added a loop so you can see how it works in a loop):

    [DllImport(@"C:\Program Files (x86)\Windows Kits\10\Redist\offreg\x64\offreg.dll", CharSet = CharSet.Unicode)]
    public static extern int OREnumKey(IntPtr Handle,
                                        int dwIndex,
                                        IntPtr lpName,
                                        ref int lpcName,
                                        IntPtr lpClass,
                                        ref int lpcClass,
                                        out long lpftLastWriteTime);
    
    [DllImport(@"C:\Program Files (x86)\Windows Kits\10\Redist\offreg\x64\offreg.dll", CharSet = CharSet.Unicode)]
    public static extern int ORQueryInfoKey(
                                IntPtr Handle,
                                IntPtr lpClass,
                                ref int lpcClass,
                                out int lpcSubKeys,
                                out int lpcMaxSubKeyLen,
                                out int lpcMaxClassLen,
                                out int lpcValues,
                                out int lpcMaxValueNameLen,
                                out int lpcMaxValueLen,
                                out int lpcbSecurityDescriptor,
                                out long lpftLastWriteTime);
    
    static void Main()
    {
        const int ERROR_MORE_DATA = 234;
        const int ERROR_NO_MORE_ITEMS = 259;
    
        var res = OROpenHive(@"c:\TEMP\NTUSER.DAT", out var h);
        if (res != 0)
            throw new Win32Exception(res);
    
        try
        {
            var clsLen = 0;
            var clsPtr = IntPtr.Zero;
            res = ORQueryInfoKey(h, IntPtr.Zero, ref clsLen, out var subKeys, out var maxSubKeyLen, out var maxClassLen, out var values, out var maxValueNameLen, out var maxValueLen, out var securityDescriptor, out var lastWriteTime);
            if (res == ERROR_MORE_DATA)
            {
                clsPtr = Marshal.AllocHGlobal(clsLen);
                try
                {
                    res = ORQueryInfoKey(h, clsPtr, ref clsLen, out subKeys, out maxSubKeyLen, out maxClassLen, out values, out maxValueNameLen, out maxValueLen, out securityDescriptor, out lastWriteTime);
                    if (res == 0)
                    {
                        var cls = Marshal.PtrToStringUni(clsPtr);
                        Console.WriteLine("Class: " + cls);
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(clsPtr);
                }
            }
    
            if (res != 0)
                throw new Win32Exception(res);
    
            var nameLen = 0;
            var i = 0;
            var namePtr = IntPtr.Zero;
    
            do
            {
                clsLen = 0;
                nameLen = 0;
                res = OREnumKey(h, i, IntPtr.Zero, ref nameLen, IntPtr.Zero, ref clsLen, out lastWriteTime);
                if (res == ERROR_NO_MORE_ITEMS)
                    break;
    
                if (res == ERROR_MORE_DATA)
                {
                    if (nameLen > 0)
                    {
                        namePtr = Marshal.AllocHGlobal(nameLen);
                    }
    
                    if (clsLen > 0)
                    {
                        clsPtr = Marshal.AllocHGlobal(clsLen);
                    }
    
                    try
                    {
                        res = OREnumKey(h, i, namePtr, ref nameLen, clsPtr, ref clsLen, out lastWriteTime);
                        if (res == 0)
                        {
                            Console.WriteLine("LastWriteTime: " + DateTime.FromFileTime(lastWriteTime));
    
                            if (namePtr != IntPtr.Zero)
                            {
                                var name = Marshal.PtrToStringUni(namePtr);
                                Console.WriteLine("Name: " + name);
                            }
    
                            if (clsPtr != IntPtr.Zero)
                            {
                                var cls = Marshal.PtrToStringUni(clsPtr);
                                Console.WriteLine("Class: " + cls);
                            }
                        }
                    }
                    finally
                    {
                        if (namePtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(namePtr);
                            namePtr = IntPtr.Zero;
                        }
    
                        if (clsPtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(clsPtr);
                            clsPtr = IntPtr.Zero;
                        }
                    }
                }
    
                if (res != 0)
                    throw new Win32Exception(res);
    
                i++;
            }
            while (true);
        }
        finally
        {
            ORCloseHive(h);
        }
    }