Wednesday, December 01, 2004

Spying on Registry Entries

I am probably letting out the best kept secret of installation industry. I always used to wonder and have now discovered how most of the installation tools spy on registry entries that are created during COM registration or similar processes without actually affecting the build system. The spying program creates temporary registry keys for each of the registry hives HKCR, HKLM, HKCU, HKU and it maps the registry hives to these temporary registry keys. It then triggers the registration function which creates registry entries withing the registry keys specified instead of including it in the hives. I came across this revelation while I was wading through the source code for Tallow in the WIX toolset.


The core of this spying exercise relies on functions exposed by the advapi32.dll. The actual hive to key mapping is performed by the RegOverridePredefKey() function. The handle to the registry key is passed by using the RegCreateKeyEx() or the RegOpenKeyEx() function. After the mapping is done, you can invoke the DllRegisterServer() function after loading the library using the LoadLibrary() function. This mapping would be active for all the registry entries created by that particular process. So out of process registration for exe files may not directly work with this method. For the sake of simplicity, I am going to extract COM Interop settings from a given assembly. So let us write a simple console app in C# to do this. This app would perform the mapping and write the registry entries to a REG file and wipe out the key after the file is written.


Extracting registration information from a DLL file is similar but involve the importing other functions from Kernel32.dll. So I am giving that a raincheck now. You can download the Wix toolset's source package if you are interested. To start off with, let us write a class with static members to import the functions from advapi32.dll and create wrappers for them. Ensure that you have System.Runtime.InteropServices namespace included.



    class OverRideRegistry
    {
        //We first declare some stuff required. The are defined in winreg.h and windows.h
        public static readonly UIntPtr HkeyClassesRoot = (UIntPtr)0x80000000;
        public static readonly UIntPtr HkeyCurrentUser = (UIntPtr)0x80000001;
        public static readonly UIntPtr HkeyLocalMachine = (UIntPtr)0x80000002;
        public static readonly UIntPtr HkeyUsers = (UIntPtr)0x80000003;

        public const uint Delete = 0x00010000;
        public const uint ReadOnly = 0x00020000;
        public const uint WriteDac = 0x00040000;
        public const uint WriteOwner = 0x00080000;
        public const uint Synchronize = 0x00100000;
        public const uint StandardRightsRequired = 0x000F0000;
        public const uint StandardRightsAll = 0x001F0000;

        public const uint GenericRead = 0x80000000;
        public const uint GenericWrite = 0x40000000;
        public const uint GenericExecute = 0x20000000;
        public const uint GenericAll = 0x10000000;

        #region
The Interop Import Stuff
        //we now import the functions exposed by advapi32.dll
        //Use RegCreateKeyEx to get handle to the openedKey
        [DllImport("advapi32.dll", EntryPoint="RegCreateKeyExW", CharSet=CharSet.Unicode, ExactSpelling=true, SetLastError=true)]
        internal static extern int RegCreateKey(UIntPtr key, string subkey, uint reserved, string className, uint options, uint desiredSam, uint securityAttributes, out IntPtr openedKey, out uint disposition);

        //This does the actual hive to key mapping
        [DllImport("advapi32.dll", EntryPoint="RegOverridePredefKey", CharSet=CharSet.Unicode, ExactSpelling=true, SetLastError=true)]
        internal static extern int RegOverridePredefKey(UIntPtr key, IntPtr newKey);

        //Like good programmers, we release our handles.
        [DllImport("advapi32.dll", EntryPoint="RegCloseKey", CharSet=CharSet.Unicode, ExactSpelling=true, SetLastError=true)]
        internal static extern int RegCloseKey(IntPtr key);
        //Our interops are done :-)
        #endregion

        //Now we actually write wrapper functions to use the imported functions
        //Wrapper for creating Registry Keys
        internal static IntPtr OpenRegistryKey(UIntPtr key, string path)
        {
            IntPtr newKey = IntPtr.Zero;
            uint disposition = 0;
            uint security = StandardRightsAll | GenericRead | GenericWrite | GenericExecute | GenericAll;
            int error = RegCreateKey(key, path, 0, null, 0, security, 0, out newKey, out disposition);
            return newKey;
        }
        //Wrapper for the mapping
        internal static void OverrideRegistryKey(UIntPtr key, IntPtr newKey)
        {
            int error = RegOverridePredefKey(key, newKey);
        }
        //Wrapper for freeing the handle
        internal static void CloseRegistryKey(IntPtr key)
        {
            int error = RegCloseKey(key);
        }
    }


Now that we have the helper class with the static methods, we can define our main Console App class, which would use the functions in the helper class to register the assembly and steal the registry entries. Let us define the functions that Map and Map the registry hives. I had initialized the RegLocation to a string "Software\\Vagmi\\RegInterop\\". So the extracted registry entries would be put in the HKLM hive within the specified path. Also not that I have mapped HKLM as the final mapping. Else all keys that are subsequently created would be created within our registry key.



        void MapRegHives()
        {
            MapRegHive(OverRideRegistry.HkeyClassesRoot,this.RegLocation+"HKCR");
            MapRegHive(OverRideRegistry.HkeyCurrentUser,this.RegLocation+"HKCU");
            MapRegHive(OverRideRegistry.HkeyUsers,this.RegLocation+"HKU");
            MapRegHive(OverRideRegistry.HkeyLocalMachine,this.RegLocation+"HKLM");
        }
        void MapRegHive(UIntPtr key, string location)
        {
            Console.WriteLine("Mapping " + key + "  to " + location);
            IntPtr createdKey=IntPtr.Zero;
            try
            {
                createdKey=OverRideRegistry.OpenRegistryKey(OverRideRegistry.HkeyLocalMachine,location);
                OverRideRegistry.OverrideRegistryKey(key,createdKey);
            }
            catch(Exception e)
            {
                Console.WriteLine("Caught exception: " + e.Message);
            }
            finally
            {
                //close key like responsible programmers
                OverRideRegistry.CloseRegistryKey(createdKey);
            }
        }
        //Functions to unmap registry hives.
        void UnMapRegHives()
        {
            OverRideRegistry.OverrideRegistryKey(OverRideRegistry.HkeyClassesRoot,IntPtr.Zero);
            OverRideRegistry.OverrideRegistryKey(OverRideRegistry.HkeyCurrentUser,IntPtr.Zero);
            OverRideRegistry.OverrideRegistryKey(OverRideRegistry.HkeyLocalMachine,IntPtr.Zero);
            OverRideRegistry.OverrideRegistryKey(OverRideRegistry.HkeyUsers,IntPtr.Zero);
        }


I then parse two command line arguments one for the DLL and another for the REG file to export. I then pass these as constructors to my class, which calls these functions.



        public RegSpyCOMInterop(string AssemblyPath, string RegFile)
        {
            try
            {
                Assembly a=Assembly.LoadFrom(AssemblyPath);
                RegistrationServices regServices=new RegistrationServices();
                //map hives to registry keys
                    MapRegHives();
                    //Register assembly for COM interop
                    regServices.RegisterAssembly(a,AssemblyRegistrationFlags.SetCodeBase);
                    //Unmap hives
                    UnMapRegHives();
                WriteToRegFile(RegFile);
            }
            catch(Exception e)
            {
                Console.WriteLine("Caught Exception : " + e.Message);
            }
        }


The WriteToRegFile() function launches regedit and exports the hive that contains our keys. It then uses simple text replacement to change the values of the exported file to make the REG file functional.



        public void WriteToRegFile(string regfile)
        {
            RegistryKey r=Registry.LocalMachine.OpenSubKey(RegLocation,false);
            PathToDelete=r.Name + "\\";
            Process pr=new Process();
            pr.StartInfo.FileName="regedit";
            pr.StartInfo.Arguments=" /e " + regfile + " " + r.Name;
            pr.Start();
            pr.WaitForExit();
            string line;
            StreamReader reader=new StreamReader(regfile);
            StreamWriter writer=new StreamWriter("TempFile.txt");
            while((line=reader.ReadLine())!=null)
            {
                writer.WriteLine(processRegistryName(line));
            }
            reader.Close();
            writer.Close();
            File.Copy("TempFile.txt",regfile,true);
            File.Delete("TempFile.txt");
            r.Flush();
            r.Close();
        }


        public String processRegistryName(string regname)
        {
            string newRegName="";
            newRegName=regname.Replace(PathToDelete,"");
            newRegName=newRegName.Replace("[" + PathToDelete.Substring(0,PathToDelete.Length-1) + "]","");
            newRegName=newRegName.Replace("HKCR","HKEY_CLASS_ROOT");
            newRegName=newRegName.Replace("HKCU","HKEY_CURRENT_USER");
            newRegName=newRegName.Replace("HKLM","HKEY_LOCAL_MACHINE");
            newRegName=newRegName.Replace("HKU","HKEY_USERS");
            return newRegName;
        }


There you have it. A command line utility to extract COM Interop Registry entries. Hope this answered some of your queries regarding registration without affecting the target system. You can find the entire source code at http://www.geekswithblogs.net/vagmi.mudumbai/articles/16581.aspx.

3 comments:

Roberto Iza Valdés said...
This comment has been removed by a blog administrator.
Roberto Iza Valdés said...

Compliments

con sultan said...

Thanks dude! Mapping HKCR, registering an assembly for use in COM, registering a custom pluggable protocol handler (e.g. CID for inline images) via IInternetSession, and then unmapping HKCR seems to be the ONLY way to achieve the actual plugging of the protocol for non-privileged accounts.

Can't thank you enough!
Cheers