Eric's Technical Outlet

Learning the hard way so you don't have to

Take Ownership of and Reset Registry Key Permissions with VB.Net

Windows programming has begun a trend away from using the registry to store application data. There are a lot of reasons for that, most of them are pretty good, and I have no intentions of debating them in this post. There are still plenty of valid reasons you might wish to use the registry. Unfortunately, the .Net Framework’s abstraction of the registry is nowhere near as robust as it is for other system objects, such as files and folders. The area that it’s most critically lacking is that the only thing you can do without an active handle to a registry key is open a handle to a registry key. Most of the time, that’s not a big deal. Any key you can’t open is probably a key you shouldn’t open. Then again, there are those few times that you need to open a key but can’t. This post is intended to show you how to do that in VB.Net. C# users should be able to read along without a lot of difficulty, although you’ll probably want to refer to PInvoke.net or a similar resource for the exact methods of calling the specified Windows API functions from within C#.

Disclaimer

Most of this is boilerplate stuff, but if you break your system or leave an easy way for someone else to break your system then I’m going to refer you to this. I make no claims of any kind that this code will do what you want. I make no claims that it is secure. This code can enable you to do things you shouldn’t be doing. Don’t yell at me about teaching the bad guys because we’ve all seen malware do the sort of thing I’m demonstrating. I’m not responsible for anything that you or anyone else does with this.

The Basic Problem This Code Intends to Solve

The .Net Framework provides two functions to access a key: RegistryKey.OpenSubKey() and RegistryKey.CreateSubKey(). Unfortunately, if you don’t have ownership or access to the key granted by inherited or explicit permissions, these functions will always fail. If your code is running under the Local System account or as a member of the Administrators group or as a user that has been specifically granted the privilege by group policy, you can take ownership of any object. With ownership, you can modify permissions on the object. Even though some of the options within the RegistryKey constructor seem to imply that it’s possible to take ownership of a key, it either does not work or the method of doing so is poorly documented. Fortunately, the Windows API supplies a function that allows you to take ownership of an object using only its name.

There Is An Easier Way

If I had to do this all over again, I would probably have spent more time investigating alternate solutions. The overall problem I’m trying to solve seems to be better handled via the registry, but it’s safe to say that almost everyone who thinks they need to use the registry does not need to use the registry. Also, knowing what I know now, I definitely would have written a C++ DLL to do this and invoked that from my VB.Net application. A great deal of the code lines are just duplicating Win32 API constructs so that the function calls work as expected. A C++ code file would be a lot shorter and cleaner, and I could have developed it in maybe a quarter of the time I took to build this. I continued plodding at it after I realized it was the hard way just because of all the Internet posts claiming that it couldn’t be done and that everyone who wants to should just give up and use the file system or My.Settings or something. Quite simply, “regedit.exe” has the ability to take ownership of a registry key and reset permissions on a registry key, therefore it is possible programmatically, so it was just a matter of putting forth the effort to figure out how to do it.

There Is Probably an Even Easier Way

Some of the functions within .Net seem to indicate that they might be used to duplicate the functionality that this code calls on the Windows API to do. None of my attempts to coerce them into behaving were successful, but maybe someone else is willing to accept the challenge of getting them to do so.

What This Code Does

This is a stand-alone code file that contains a module (single-instance class) with a single externally exposed function (SeizeRegistryKey). You must supply it with the base registry key, the name of the subkey you wish to access, and an empty buffer string for error messaging. The function will, acting on behalf of the user account that is running the code, attempt to take ownership of the specified registry key, strip all assigned permissions and inheritances, and gain Full Control over it and its values. It will return a boolean value: true if the function worked, false if otherwise. You can read the value of your string buffer to see what went wrong, if anything.

What This Code Does Not Do

This code does not validate that the passed-in key exists. It will exit gracefully if it doesn’t, but it will waste some CPU cycles first. It’s expected that this code will be called as a back-up plan to other code that tries to do things the easy way, not be the first tool of choice. This code will not give “Take Ownership” rights to a user that does not have it. There is a spot in the code that appears to do something like that, but what it’s actually doing is conferring that right from the current user to the process that is running as the current user. If the current user doesn’t have that right, then the code will fail and generate an error. This code sets the user’s permissions on the current key to inherit downwards, but it will not propagate that inheritance. That means that if a subkey is not already inheriting, it will not be set to inherit. This code is not thread-safe and it is entirely possible that it could collide with other processes because the registry is, by nature, a shared-access beast. There is sufficient error-checking as the function progresses to prevent a referencing application from choking to death, but if the state of an object changes between one line of code and the next, the code will probably fail.

What This Code Could Do

I’ve put in a several Win32 enumerators and structures where single-line constants would have served for the simple fact that you may wish to use this as a base for operations I hadn’t considered or needed. The meat of the entire process is found in the “SetNamedSecurityInfo” API function, which allows you to tinker with permissions and privileges for objects that you don’t have a handle to, making it an extremely powerful function. This particular code sample only operates on registry objects, but with a few modifications, this code could be made to operate on just about any Windows object. One obvious place this code could use improvement is its heavy-handedness. The current account might already be owner or be in a group that is the owner so there might not be a need to take ownership; you just have to change permissions. This code simply takes ownership without checking, asking or verifying. It also strips all access rights, both inherited and explicit, which may be a lot more than you want to do. The .Net Framework provides several methods to allow you to fairly easily add and remove access rules, so refine the code to your needs. The various steps of the single function could be broken into separate functions. Beyond reducing the heavy-handedness, this could also be used to recursively adjust permissions on subkeys.

A Brief Code Walk-through

The entire process occurs in four fairly simple steps. The first step is to retrieve the locally-unique ID for the take ownership privilege. It is named “SeTakeOwnershipPrivilege” and if you ask, Windows will tell you what its LUID is:

LookupPrivilegeValue(Nothing, _
 "SeTakeOwnershipPrivilege", _
 TakeOwnershipLUIDandAttr.Luid)

In the full code, a structure was designed to hold an LUID and its attributes; that was given to the third parameter of the LookupPrivilegeValue function, which will populate it with the LUID of “SeTakeownershipPrivilege. Step 2 is to take the LUID and set an attribute on it so that Windows knows that the intention is to enable the privilege indicated in the LUID on the current process, then apply the modified LUID and attributes set to the current process’s token:

NewState.Privileges = New LUID_AND_ATTRIBUTES() _
   {TakeOwnershipLUIDandAttr}
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
AdjustTokenPrivileges( _
   CurrentUserToken, _
   False,_
   NewState, _
   Marshal.SizeOf(PreviousState), _
   PreviousState, ReturnLength)

The relevant portions of the function in the last line in the code are the first three parameters: the token represents the current user within the context of the current process, the “FALSE” parameter indicates that rights are not to be taken away, only added, and the new state as defined by the combination of the LUID and attributes is applied to the token that represents the user within the running process. If you’re curious about the last three parameters, read the comments in the full code file. They are not actually used by this code. Step 3 is to take ownership of the key. This is primarily to ensure that permissions can be set.

SetNamedSecurityInfo( _
   FullKeyName, _
   SE_OBJECT_TYPE.SE_REGISTRY_KEY, _
   SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION, _
   CurrentUserSDDL, _
   Nothing, Nothing, Nothing)

The above code reads as: “set the security information on the object with the name of ‘FullKeyName’ which is of type ‘REGISTRY_KEY’ so that its owner is the user indicated by ‘CurrentUserSDDL'” and leave all other security info fields as they are. The uppercase parameters are enumerators set in the code file. The CurrentUserSDDL is just the binary form of the security identifier of the current user. The three “Nothing” parameters are for other items that could be changed using this command but are more easily handled using .Net commands. Now that you’ve got ownership, you can change permissions, but you must indicate that’s what you wish to do. Asking for other permissions at this time will fail unless there is a security entry that permits them. This code assumes that there is no such entry.

   Subkey = BaseKey.OpenSubKey( _
   RegistrySubKeyName, _
   RegistryKeyPermissionCheck.ReadWriteSubTree, _
   RegistryRights.ChangePermissions)

At this point, the “Subkey” variable holds a reference to the registry key in question and your code is allowed to change the permissions on it. The included code just strips out all inherited and explicit permissions and replaces them with a single entry that grants the current user full control over the key and all contained objects that have inheritance enabled:

KeyAccessSettings = Subkey.GetAccessControl
KeyAccessSettings.SetAccessRuleProtection(True, False)
KeyRuleList = KeyAccessSettings.GetAccessRules(True, True, _
   GetType(SecurityIdentifier))
For Each RuleEntry As RegistryAccessRule In KeyRuleList
   KeyAccessSettings.RemoveAccessRuleSpecific(RuleEntry)
Next RuleEntry
KeyFullControlRule = New RegistryAccessRule( _
   CurrentUserIdentity, _
   RegistryRights.FullControl, _
   InheritanceFlags.ContainerInherit, _
   PropagationFlags.None, _
   AccessControlType.Allow)
KeyAccessSettings.SetAccessRule(KeyFullControlRule)
Subkey.SetAccessControl(KeyAccessSettings)

The linked code file contains full comments for the above, but the basic process is to strip inheritance, remove all existing permission entries, and add one that grants the current user full control.

The Full Code File

The code on this page and the full file are licensed under a Creative Commons Attribution Share-Alike license. You may create derivatives and use them in your own code, including commercial code, under the conditions set forth in that license.

' RegistryOwnership v1.01
' v1.0 -- June 12, 2012
' v1.01 -- July 10, 2015 -- corrected HKLM access; bug report by 'Jonas'
' This work by Eric Siron is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
' http://creativecommons.org/licenses/by-sa/3.0/
Imports Microsoft.Win32
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Security.AccessControl
Imports System.Security.Principal

Module RegistryOwnership
	Private Const WIN_NT_ANYSIZE_ARRAY As Integer = 1
	Private Const WIN32_NO_ERROR = 0
	Private Const SE_PRIVILEGE_ENABLED As Integer = &H2 ' WinNT.h line 7660; there are other possibilities, but none useful in this context

	' most of the Win32 objects are directly from WinNT.h or AccCtrl.h
	#Region "Win32 Enums"
	''' <summary>
	''' Indicates which object type SetNamedSecurityInfo will be operating on
	''' </summary>

	''' <remarks></remarks>
	Private Enum SE_OBJECT_TYPE ' the only type used in this assembly is SE_REGISTRY_KEY; list included for completeness and expandability
		SE_UNKNOWN_OBJECT_TYPE = 0
		SE_FILE_OBJECT
		SE_SERVICE
		SE_PRINTER
		SE_REGISTRY_KEY
		SE_LMSHARE
		SE_KERNEL_OBJECT
		SE_WINDOW_OBJECT
		SE_DS_OBJECT
		SE_DS_OBJECT_ALL
		SE_PROVIDER_DEFINED_OBJECT
		SE_WMIGUID_OBJECT
		SE_REGISTRY_WOW64_32KEY
	End Enum

	''' <summary>
	''' Indicates which information types the SetNamedSecurityInfo function will be manipulating
	''' </summary>

	''' <remarks></remarks>
	Private Enum SECURITY_INFORMATION As UInteger
		OWNER_SECURITY_INFORMATION = &H1L
		GROUP_SECURITY_INFORMATION = &H2L
		DACL_SECURITY_INFORMATION = &H4L
		SACL_SECURITY_INFORMATION = &H8L
		LABEL_SECURITY_INFORMATION = &H10L
		PROTECTED_DACL_SECURITY_INFORMATION = &H80000000L
		PROTECTED_SACL_SECURITY_INFORMATION = &H40000000L
		UNPROTECTED_DACL_SECURITY_INFORMATION = &H20000000L
		UNPROTECTED_SACL_SECURITY_INFORMATION = &H10000000L
	End Enum
	#End Region

	#Region "Win32 Structures"
	''' <summary>
	''' Duplicate of the Win32 API's locally unique identifier structure
	''' </summary>

	''' <remarks></remarks>
	<StructLayout(LayoutKind.Sequential)> _
	Private Structure LUID
		Dim LowPart As UInt32
		Dim HighPart As UInt32
	End Structure

	''' <summary>
	''' Structure that holds an LUID and its attributes.
	''' </summary>

	''' <remarks></remarks>
	<StructLayout(LayoutKind.Sequential)> _
	Private Structure LUID_AND_ATTRIBUTES
		Dim Luid As LUID
		Dim Attributes As UInt32
	End Structure

	''' <summary>
	''' An array structure that indicates which privileges a token does/should have
	''' </summary>

	''' <remarks>Adjust SizeConst for uses beyond original design</remarks>
	<StructLayout(LayoutKind.Sequential)> _
	Private Structure TOKEN_PRIVILEGES
		Dim PrivilegeCount As UInt32
		<MarshalAs(UnmanagedType.ByValArray, SizeConst:=WIN_NT_ANYSIZE_ARRAY)> Dim Privileges() As LUID_AND_ATTRIBUTES
	End Structure
	#End Region
	#Region "Win32 Functions"

	''' <summary>
	''' Win32 API call to find the locally-unique ID of a named privilege
	''' </summary>

	''' <param name="lpSystemName">The system to find the privilege for. Use Nothing for the local computer.</param>
	''' <param name="lpName">The name of the privilege to lookup</param>
	''' <param name="lpLuid">OUT: LUID structure</param>
	''' <returns>True for success; false for failure. Use Marshal.GetLastWin32Error to determine error code.</returns>
	''' <remarks></remarks>
	<DllImport("advapi32.dll", SetLastError:=True)> _
	Private Function LookupPrivilegeValue(ByVal lpSystemName As String, ByVal lpName As String, ByRef lpLuid As LUID) As Boolean
	End Function

	''' <summary>
	''' Changes privileges on an active user token.
	''' </summary>

	''' <param name="TokenHandle">Handle to the user token</param>
	''' <param name="DisableAllPrivileges">True to disable all privileges.</param>
	''' <param name="NewState">TOKEN_PRIVILEGES structure with desired privilege set.</param>
	''' <param name="BufferLength">Size of the structure that will be passed in the PreviousState parameter.</param>
	''' <param name="PreviousState">TOKEN_PRIVILEGES structure to hold the user's privileges as they were prior to
	''' this function call.</param>
	''' <param name="ReturnLength">OUT: The size of the PreviousState variable after the function runs, or in the event
	''' of an error, the minimum size that the PreviousState variable should have been.</param>
	''' <returns>0 for success, a non-zero error code that can initialize a new Win32Exception for errorchecking.</returns>
	''' <remarks></remarks>
	<DllImport("advapi32.dll", SetLastError:=True)> _
	Private Function AdjustTokenPrivileges(ByVal TokenHandle As IntPtr, ByVal DisableAllPrivileges As Boolean, ByRef NewState As TOKEN_PRIVILEGES, ByVal BufferLength As Integer, <Out()> ByRef PreviousState As TOKEN_PRIVILEGES, <Out()> ByRef ReturnLength As IntPtr) As Boolean
	End Function

	''' <summary>
	''' Applies security settings to an object by its name.
	''' </summary>

	''' <param name="pObjectName">Name of the object.</param>
	''' <param name="ObjectType">Type of the object</param>
	''' <param name="SecurityInfo">Flag indicating which security item(s) will be modified.</param>
	''' <param name="psidOwner">SID of the new owner, if ownership is changing.</param>
	''' <param name="psidGroup">SID of the object's primary group, if it is changing.</param>
	''' <param name="pDacl">Discretionary access list to be set on the object, if it is changing.</param>
	''' <param name="pSacl">Security access list to be set on the object, if it is changing.</param>
	''' <returns>0 for success, a non-zero error code that can initialize a new Win32Exception for errorchecking.</returns>
	''' <remarks></remarks>
	Private Declare Auto Function SetNamedSecurityInfo Lib "advapi32.dll" (ByVal pObjectName As String, ByVal ObjectType As SE_OBJECT_TYPE, ByVal SecurityInfo As SECURITY_INFORMATION, ByVal psidOwner As Byte(), ByVal psidGroup As IntPtr, ByVal pDacl As IntPtr, ByVal pSacl As IntPtr) As Integer
	#End Region

	''' <summary>
	''' Enumerator that allows calling functions to indicate the base registry key to work with.
	''' </summary>

	''' <remarks>.Net functions typically use the HKEY_ prefix while the SetNamedSecurityInfo used here does not, so this
	''' can help avoid confusion.
	''' </remarks>
	Friend Enum BaseKeySelector
		ClassesRoot
		CurrentConfig
		CurrentUser
		LocalMachine
		Users
		' allow for DynData as well?
	End Enum

	''' <summary>
	''' Takes ownership of the indicated registry key for the current user and adds an access control entry to the key's
	''' access control list that gives the current user full control over the key.
	''' </summary>

	''' <param name="RegistryBaseKeyName">An enumerator that indicates which registry base key to work with.</param>
	''' <param name="RegistrySubKeyName">Subkey to use. Format example: "Software\MyApplication\MyKey"</param>
	''' <param name="ErrorMessage">This string will be populated with any error conditions that the function traps.</param>
	''' <returns>True if the function succeeed. If False, check the value of ErrorMessage.</returns>
	''' <remarks></remarks>
	Friend Function SeizeRegistryKey(ByVal RegistryBaseKeyName As BaseKeySelector, ByRef RegistrySubKeyName As String, ByRef ErrorMessage As String) As Boolean
		'' Variable Declarations ''
		Dim Win32ReturnValue As Integer = 0 ' Windows APIs return numeric values from most functions.
		Dim Win32ErrorCode As Integer = 0 ' Used to hold the last recorded Win32 error code
		Dim CurrentUserToken As IntPtr = IntPtr.Zero ' Security token of the user this process is running as
		Dim CurrentUserIdentity As SecurityIdentifier ' Identity of "" ""
		Dim CurrentUserSDDL As Byte() = Nothing ' SDDL of "" ""
		Dim CurrentUsername As String = "" ' Full name of current user in DOMAIN\username format
		Dim TakeOwnershipLUIDandAttr As LUID_AND_ATTRIBUTES ' container for the SeTakeOwnership LUID and its attributes
		Dim NewState As TOKEN_PRIVILEGES ' Desired privilege set for the token
		Dim PreviousState As TOKEN_PRIVILEGES ' Previous privilege set for the token
		Dim ReturnLength As IntPtr = IntPtr.Zero ' Some Windows APIs set a number indicated how much data they returned, or tried to return
		Dim FullKeyName As String ' Combination of the registry base key and the subkey
		Dim BaseKey As RegistryKey ' The Microsoft.Win32.Registry representation of the base key
		Dim Subkey As RegistryKey ' The Microsoft.Win32.Registry representation of the key to be manipulated
		Dim KeyAccessSettings As RegistrySecurity ' Encapsulation of security settings on the registry key
		Dim KeyRuleList As AuthorizationRuleCollection ' Encapsulation of authorization rules on the registry key (essentially ACEs)
		Dim KeyFullControlRule As RegistryAccessRule ' Registry access rule that will give the current user full control

		'' Initializations ''
		ErrorMessage = "No error"
		Subkey = Nothing

		'' Step 1
		'' To see if the current user has the SeTakeOwnershipPrivilege, it is first necessary to determine
		'' what this computer calls that privilege (indicated by its LUID).
		TakeOwnershipLUIDandAttr = New LUID_AND_ATTRIBUTES
		If Not LookupPrivilegeValue(Nothing, "SeTakeOwnershipPrivilege", TakeOwnershipLUIDandAttr.Luid) Then
			ErrorMessage = String.Format("Cannot determine identifier for SeTakeOwnershipPrivilege: {0}", (New Win32Exception(Marshal.GetLastWin32Error)))
			Return False
		End If

		'' Step 2
		'' With the LUID of SeTakeOwnershipPrivilege in hand, the next step is to enable the privilege
		' for the user within this process.
		' Get the user's token first. Tokens taken this way do not need to be manually released. Note that GetCurrent()
		' takes binary flags, so the "Or" is combining the two indicated access levels.
		CurrentUserToken = WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges Or TokenAccessLevels.Query).Token
		NewState = New TOKEN_PRIVILEGES ' Create an empty TOKEN_PRIVILEGES
		NewState.PrivilegeCount = 1 ' This will always be 1 in this usage, but structures can't have initializers without using Shared, which may have unexpected side effects in this context
		NewState.Privileges = New LUID_AND_ATTRIBUTES() {TakeOwnershipLUIDandAttr} ' Create the privilege set directly from the retrieved LUID; the only thing of meaning in here is SeTakeOwnershipPrivilege's LUID
		NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED ' this indicates to AdjustTokenPrivileges what is to change
		PreviousState = New TOKEN_PRIVILEGES ' Will hold the privilege state of the token prior to modifications

		' Documentation for the Win32 API indicates that passing a zero for BufferLength allows you to send in a
		' NULL (Nothing in VB) for PreviousState and ReturnLength. However, attempting this always causes this function
		' to return an error that insufficient space was provided for PreviousState. So, even though this assembly
		' completely ignores the value of PreviousState, it must be captured.
		If Not AdjustTokenPrivileges(CurrentUserToken, False, NewState, Marshal.SizeOf(PreviousState), PreviousState, ReturnLength) Then
			Win32ErrorCode = Marshal.GetLastWin32Error
			If Win32ErrorCode = 122 Then ' PreviousState variable isn't large enough for the amount of data that AdjustTokenPrivileges is returning
				ErrorMessage = String.Format("The ""PreviousState"" variable passed to AdjustTokenPrivileges is not large enough. Its size was {0}. The required size was {1}", Marshal.SizeOf(PreviousState), ReturnLength)
				' TODO: Else/Select Case: Set up traps for common returns, like security problems
			Else
				ErrorMessage = String.Format("An error occurred while attempting to adjust privileges for {0}: {1}", WindowsIdentity.GetCurrent.User, (New Win32Exception(Win32ErrorCode).Message))
			End If
			Return False
		End If

		'' Step 3
		'' The user token is now in a state where it can take ownership of objects in the system. Seize the registry key.
		' Convert the passed-in registry key parts to a single string and get the base key
		Select Case RegistryBaseKeyName ' TODO: add an entry for DynData? edge-case usage probably not worth the effort
			Case BaseKeySelector.ClassesRoot
				FullKeyName = "CLASSES_ROOT"
				BaseKey = Registry.ClassesRoot
			Case BaseKeySelector.CurrentConfig
				FullKeyName = "CURRENT_CONFIG"
				BaseKey = Registry.CurrentConfig
			Case BaseKeySelector.CurrentUser
				FullKeyName = "CURRENT_USER"
				BaseKey = Registry.CurrentUser
			Case BaseKeySelector.LocalMachine
				FullKeyName = "MACHINE"
				BaseKey = Registry.LocalMachine
			Case BaseKeySelector.Users
				FullKeyName = "USERS"
				BaseKey = Registry.Users
			Case Else
				ErrorMessage = "Invalid registry base key selected"
				Return False
		End Select

		' TODO: the "RegistrySubKeyName" variable is the most fragile part of this function; consider adding error-checking,
		' convert signature to ByVal (makes a copy, potentially wasteful of memory) for string manipulation operations
		FullKeyName &= "\" & RegistrySubKeyName
		' need the binary form of the current user's SID for SetNamedSecurityInfo(S-1-5-XX-XXXXXXXXXX...)
		CurrentUserIdentity = New SecurityIdentifier(WindowsIdentity.GetCurrent.User.Value) ' get the identity object first
		ReDim CurrentUserSDDL(CurrentUserIdentity.BinaryLength) ' prepare the SDDL binary array to hold it
		CurrentUserIdentity.GetBinaryForm(CurrentUserSDDL, 0) ' retrieve the binary SDDL and place it in the array

		' take ownership of the registry key
		Win32ReturnValue = SetNamedSecurityInfo(FullKeyName, SE_OBJECT_TYPE.SE_REGISTRY_KEY,
		SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION, CurrentUserSDDL, Nothing, Nothing, Nothing)
		If Win32ReturnValue <> WIN32_NO_ERROR Then
			ErrorMessage = String.Format("Error taking ownership of {0}: {1}", FullKeyName, (New Win32Exception(Win32ReturnValue).Message))
			Return False
		End If

		'' Step 4
		'' Having ownership is great, but all that does on its own is allow the current user to change permissions on the object.
		'' Without an explicitly granted permission, the current user will still be unable to change any values contained in
		'' the key. However, it is now possible to get a handle to the key to manipulate permissions.
		Try
			Subkey = BaseKey.OpenSubKey(RegistrySubKeyName, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.ChangePermissions) ' get a handle to the key, explicitly indicate the need for ChangePermisssions rights
			If Subkey Is Nothing Then
				' included for completeness as this should never happen; the only possibility is that another process deleted the key
				Throw New Exception("The specified registry key could not be found.")
			End If
			KeyAccessSettings = Subkey.GetAccessControl ' copy the current security settings into KeyAccessSettings
			KeyAccessSettings.SetAccessRuleProtection(True, False) ' True in the first parameter means that in case of an inheritance conflict, inheritance loses (except in the case of a deny). False in the second parameter means to remove inherited rules. This is the only combination that will strip an inherited Deny.
			KeyRuleList = KeyAccessSettings.GetAccessRules(True, True, GetType(SecurityIdentifier)) ' this is effectively an abstraction of the ACL on the key

			' clean all rules from the list
			For Each RuleEntry As RegistryAccessRule In KeyRuleList
				KeyAccessSettings.RemoveAccessRuleSpecific(RuleEntry)
			Next RuleEntry
			KeyFullControlRule = New RegistryAccessRule(CurrentUserIdentity, RegistryRights.FullControl, InheritanceFlags.ContainerInherit, PropagationFlags.None, AccessControlType.Allow) ' create a new rule that sets the current user's permissions as Full Control: Allow with downward inheritance enabled
			KeyAccessSettings.SetAccessRule(KeyFullControlRule) ' place the Full Control rule into the list (now the sole entry)
			Subkey.SetAccessControl(KeyAccessSettings) ' replace the key's security settings with the new one (no inheritance, the current user has full control, no one else has any permissions)
		Catch ex As Exception
			ErrorMessage = String.Format("An error occurred while attempting to access and set permissions for {0}\{1}: {2}", BaseKey.Name, RegistrySubKeyName, ex.Message)
			Return False
		End Try
		' All done
		Return True
	End Function
End Module

17 responses to “Take Ownership of and Reset Registry Key Permissions with VB.Net

  1. ADudeWhoWondersHow January 27, 2019 at 8:48 am

    But how can i bring it back to normal state? Wouldnt like to remove Microsoft reg dirs, of which i took ownership, which i could do with reg dirs which i created only for my own prog …

    Like

    • Eric Siron January 27, 2019 at 7:10 pm

      This does not remove any registry keys. It only impacts permissions.
      If you want to revert changes, you will need to implement something more elaborate than this. You will need to save the existing state somewhere and revert afterward, if necessary.

      Like

  2. Ibrahim D October 29, 2017 at 7:27 pm

    You are a genius Eric!
    I wasted a whole day trying to solve this “cant write to registry error”, reading all stackoverflow answers. And NONEEEE of them worked.
    Or I can say non of the so called programmers on Stackoverflow had 5% of both your experience and understanding of all possible causes of such errors.
    With such deep understanding on how OS security and registry works, you are the boss! and you inspired me to dig deep and learn more about it myself!

    My recommendation though is, create your own blog on your own domain name, add a donation button because I would donate for sure!!!!

    Thanks a million, you saved my deadline.

    BTW for people who are asking how to use this class, here is my VB Code:
    Dim errstr As String = “” RegistryOwnership.SeizeRegistryKey(BaseKeySelector.LocalMachine, “SYSTEM\blah blah\blah blah”, errstr)
    Then
    dim unlockedkey as RegistryKey = Registry.LocalMachine.OpenSubKey(YOUR KEY YOU ADDED ABOVE, True)
    Now you can write to the “unlockedkey”

    Like

  3. x220 December 22, 2016 at 4:36 pm

    Perfekt! and excellent documentation.. 🙂 thx

    Like

  4. Ralph October 17, 2016 at 4:39 pm

    Thanks but REALLY need and example of how to use this. IE… IF the key I want to take ownership of is “Current_User\software\microsoft\THEKEY” and the user or group I want to give ownership to is “administrators” and I want to take ownership after I click on “button1” on “form1”, what is the syntax for doing it?

    Thanks,

    Ralph

    Like

  5. Alejandro March 12, 2016 at 4:02 pm

    as I apply this? some example, thanks

    Like

  6. Jonas July 9, 2015 at 5:49 am

    Excellent work!
    I fixed the “Local_Machine” issue:
    You should use: FullKeyName = “MACHINE” instead of ‘”LOCAL_MACHINE”.
    Then it works perfectly!
    See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379593(v=vs.85).aspx

    Like

  7. nobe0 March 16, 2015 at 4:16 pm

    Hi, thank you for sharing this code with us. Unfortunately it didn’t work for me without giving any error and didn’t set the user as ownership of the regkey

    Like

    • Eric Siron March 18, 2015 at 7:13 am

      That hasn’t been my experience, so without something more to go on, I don’t think that I can help you. You’re certain that the error field is not being populated?

      Like

  8. Warren March 10, 2014 at 2:38 pm

    Hi Eric,

    This code works well on my PC. I tested it with a user with no permissions and a user with full admin permissions. When I sent my app to another user on a similar WIN7 PC, it did not work at all. I thought it had to be a mistake until I saw for myself. Do you have any clue as to what conditions would this code not work?

    Many thanks,

    Like

    • Eric Siron March 10, 2014 at 2:51 pm

      Not really, no, but I didn’t put a whole lot of error checking into it either. Pretty much if the user account it’s being run under has the authority to use the Take Ownership system privilege, it should work. Is there anything in the ErrorMessage field?

      Like

  9. Scotty October 29, 2012 at 1:35 am

    Excellent code, this really helped me modify some keys i couldn’t access with my current code. But i receive an error 87 (Parameters issue) on ALL Local.Machine entries i tried. But my current code allows me to modify these with normal methods, so i have added an exception on this error number, so it continues.

    I am running this on Win7 x64 trying to access:
    LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run
    and trying to remove an entry for my firewall (ZoneAlarm), which is the only one that stays in the list
    (* this may be due to ZoneAlarm re-adding the entry faster than i can check the registry)

    Apart from that, great job, much appriciated

    Like

  10. Steffen August 27, 2012 at 3:57 am

    First, let me say that I usually never comment on things like this. However, I will make an exception seeing as this code is a work of brilliance. I’ve been looking for a way to change ownership programatically for weeks, and finally I stumbled upon this. Thank you!

    Like

  11. Dani August 25, 2012 at 9:20 pm

    Hi!!
    I have used that code but after step 3 I am receiving the Win32ReturnValue=87 after use
    the SetNamedSecurityInfo. Is there other way to fix it?

    Thank you very much!!

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.