Search code examples
c#winapiregistryregistrykey

How to delete registry symbolic link key from C#: "An error is preventing this key from being opened. Details: Access is denied"


I created a symbolic registry key by using the NtObjectManager library like that:

using NtApiDotNet;
using System;

namespace poc
{
    class Program
    {
        const string SrcKey = @"HKEY_CURRENT_USER\SOFTWARE\ABC";
        const string TargetKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\XYZ";

        static NtKey CreateSymbolicLink(string name, string target)
        {
            name = NtKeyUtils.Win32KeyNameToNt(name);
            target = NtKeyUtils.Win32KeyNameToNt(target);
            return NtKey.CreateSymbolicLink(name, null, target);
        }

        static void Main(string[] args)
        {
           var link = CreateSymbolicLink(SrcKey, TargetKey)
        }
    }
}

When I tried to delete the key from Registry (Regedit.exe) it failed with error:

ABC cannot be opened. An error is preventing this key from being opened. Details: Access is denied

I tried to delete it even with SYSTEM permissions (using psexec to launch a SYSTEM cmd) but I still received the same error.

The function NtKey.CreateSymbolicLink is calling SetSymbolicLinkTarget which calls eventually to SetValue like that:

SetValue(SymbolicLinkValueName, RegistryValueType.Link, Encoding.Unicode.GetBytes(target), throw_on_error);  

I didn't figure out yet how to delete it.
I found an answer about deleting symbolic registry key with C++ but it just calls lpfnZwDeleteKey and I don't know what is the equivalent to C#.

I tried the function NtKey.UnloadKey function, I thought it might help but it didn't.


Solution

  • I was able to delete it using James's tool CreateRegSymlink like that:

    CreateRegSymlink.exe -d "HKCU\Software\XYZ"   
    

    I noticed that it is being done by calling DeleteRegSymlink.
    When I checked what is inside it, I noticed it convert the registry path to a real path by calling RegPathToNative:

    bstr_t symlink = RegPathToNative(lpSymlink);  
    

    Here you can see what the RegPathToNative work.
    Then it calls:

    InitializeObjectAttributes(&obj_attr, &name, OBJ_CASE_INSENSITIVE | OBJ_OPENLINK, nullptr, nullptr);  
    

    Which is I think where the magic happens.
    If you have any suggestion how to find the real link from a symbolic registry path, let me know.


    EDIT(10/1/2022) - thanks to @RbMm:
    I created a function to open symlink using REG_OPTION_OPEN_LINK and then deletes it with ZwDeleteKey but the important thing was to set the rights to RegistryRights.Delete as @RbMm mentioned:

    const int REG_OPTION_OPEN_LINK = 0x0008;
    
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
    static extern int RegOpenKeyExW(SafeRegistryHandle hKey, String lpSubKey,
        int ulOptions, int samDesired, out SafeRegistryHandle hkResult);
    
    
    [DllImport("ntdll.dll")]  
    private static extern int ZwDeleteKey(SafeRegistryHandle hKey);  
    
    
    public static RegistryKey OpenSubKeySymLink(this RegistryKey key, string name, RegistryRights rights = RegistryRights.ReadKey, RegistryView view = 0)
    {
        var error = RegOpenKeyExW(key.Handle, name, REG_OPTION_OPEN_LINK, ((int)rights) | ((int)view), out var subKey);
        if (error != 0)
        {
            subKey.Dispose();
            throw new Win32Exception(error);
        }
        return RegistryKey.FromHandle(subKey);  // RegistryKey will dispose subKey
    }
    
    static void Main(string[] args)
    {
        RegistryKey key;
        key = OpenSubKeySymLink(Microsoft.Win32.Registry.CurrentUser, @"SOFTWARE\Microsoft\Windows\ABC", RegistryRights.Delete, 0);
        ZwDeleteKey(key.Handle);
    }