This is the Native Registry Editor (NtRegEdit) article I wrote that uses the CNtRegistry
class.
Important notice: Any registry manipulation can cause harm to your system and make it so it doesn't start or run properly. Back-up your registry before using this class. I am not responsible for any damage it causes.
Introduction
There are many registry classes out there, but none (that I know of) that use NT Native API calls to manipulate the registry. Normally, we use the Microsoft APIs to do the work, but they are a bit uncomfortable to use (to say the least), especially when it comes to copying, searching, and deleting keys and values. I also liked the simple example for RegHide by SysInternals that hides registry keys (in a matter of speaking).
Now, I use Robert Pittenger's CRegistry
class (found here at CodeProject) whenever I write something that accesses the registry, because of its simplicity. I also liked the idea of hiding registry keys/values from the Registry Editor (RegEdit) by Microsoft, so I decided to combine the two and write a class that does both but only uses the NT Native Registry APIs to do it. This led to the creation of the CNtRegistry
class.
Hidden Registry Keys, you say?
SysInternals says it the best (see below - verbatim from their website):
A subtle but significant difference between the Win32 API and the Native API (see Inside the Native API for more information on this largely undocumented interface) is the way that names are described. In the Win32 API, strings are interpreted as NULL-terminated ANSI (8-bit) or wide character (16-bit) strings. In the Native API, names are counted as Unicode (16-bit) strings. While this distinction is usually not important, it leaves open an interesting situation: there is a class of names that can be referenced using the Native API, but that cannot be described using the Win32 API.
How is this possible? The answer is that a name which is a counted as a Unicode string can explicitly include NULL characters (0) as part of the name. For example, "Key\0". To include the NULL at the end, the length of the Unicode string is specified as 4. There is absolutely no way to specify this name using the Win32 API since if "Key\0" is passed as a name, the API will determine that the name is "Key" (3 characters in length) because the "\0" indicates the end of the name.
When a key (or any other object with a name such as a named Event, Semaphore, or Mutex) is created with such a name, any application using the Win32 API will be unable to open the name, even though they might seem to see it.
This is where you can get a copy of RegHide from SysInternals.
What can it do?
The CNtRegistry
class has some useful features ... You can:
- Copy Keys and Values (including hidden ones) with in the same RootKey or across RootKeys.
BOOL CopyKeys(CString csSource, CString csTarget, BOOL bRecursively);
BOOL CopyValues(CString csSource, CString csTarget, CString csValueName, CString csNewValueName);
- Delete Keys and Values (including hidden ones and recursively).
BOOL DeleteKey (CString csKey);
BOOL DeleteKeysRecursive (CString csKey);
BOOL DeleteValue (CString csName);
- Rename Keys and Values.
BOOL RenameKey(CString csFullKey, CString csNewKeyName);
BOOL RenameValue(CString csOldName, CString csNewName);
- Search for Keys, Value names, and Values. This allows you to do searches for certain "String" occurrences. It can also Find hidden Keys. Both functions (Search/Find) can perform them recursively, but only with in the current RootKey.
BOOL Search (CString csString, CString csStartKey, CStringArray& csaResults, int nRegSearchType=3, BOOL bCaseSensitive = TRUE);
BOOL FindHiddenKeys (CString csKey, BOOL bRecursive, CStringArray& csaResults);
- Read and Write Values. Possible values/types are:
- Binary (
REG_BINARY, REG_RESOURCE_LIST, REG_FULL_RESOURCE_DECRIPTOR, REG_RESOURCE_REQUIREMENTS_LIST, REG_NONE
)
UCHAR* ReadBinary(CString csKey, CString csName, UINT& uiLength);
BOOL WriteBinary(CString csKey, CString csName, UCHAR* pValue, UINT uiLength);
- DWORD (
REG_DWORD
)
DWORD ReadDword(CString csKey, CString csName, DWORD dwDefault);
BOOL WriteDword(CString csKey, CString csName, DWORD dwValue);
- String (
REG_SZ
, REG_EXPAND_SZ
, REG_MULTI_SZ
)
CString ReadString(CString csKey, CString csName, CString csDefault);
BOOL ReadMultiString(CString csKey, CString csName, CStringArray& csaReturn);
BOOL WriteString(CString csKey, CString csName, CString csValue);
BOOL WriteExpandString(CString csKey, CString csName, CString csValue);
BOOL WriteMultiString(CString csKey, CString csName, CStringArray& csaValue);
- int (
REG_DWORD
)
int ReadInt(CString csKey, CString csName, int nDefault);
BOOL WriteInt(CString csKey, CString csName, int nValue);
- float (
REG_BINARY
)
double ReadFloat(CString csKey, CString csName, double fDefault);
BOOL WriteFloat(CString csKey, CString csName, double fValue);
- BOOL (
REG_DWORD
)
BOOL ReadBool(CString csKey, CString csName, BOOL bDefault);
BOOL WriteBool(CString csKey, CString csName, BOOL bValue);
- COleDateTime (
REG_BINARY
)
COleDateTime ReadDateTime(CString csKey, CString csName, COleDateTime dtDefault);
BOOL WriteDateTime(CString csKey, CString csName, COleDateTime dtValue);
- COLORREF (
REG_BINARY
)
COLORREF ReadColor(CString csKey, CString csName, COLORREF rgbDefault);
BOOL WriteColor(CString csKey, CString csName, COLORREF rgbValue);
- Objects (
REG_BINARY
)
BOOL ReadFont(CString csKey, CString csName, CFont* pFont);
BOOL WriteFont(CString csKey, CString csName, CFont* pFont);
BOOL ReadPoint(CString csKey, CString csName, CPoint* pPoint);
BOOL WritePoint(CString csKey, CString csName, CPoint* pPoint);
BOOL ReadSize(CString csKey, CString csName, CSize* pSize);
BOOL WriteSize(CString csKey, CString csName, CSize* pSize);
BOOL ReadRect(CString csKey, CString csName, CRect* pRect);
BOOL WriteRect(CString csKey, CString csName, CRect* pRect);
- Display errors that come from the calls to Native APIs in nt.dll.
- Provide a default Value in case the operation fails.
- Enable privileges for the current user which gives them the ability (if it doesn't exist already) to backup/restore Hives/Keys.
Using the code
Using the CNtRegistry
class is actually very simple. Once you have declared CNtRegistry
, initialize the class by calling InitNtRegistry()
, call SetRootKey
, then SetKey
to set "SOFTWARE\MyApp". You can also use SetKey(HKEY hRoot, CString strKey, BOOL bCanCreate, BOOL bCanSaveCurrentKey)
which simply combines the two functions (SetRootKey
and SetKey
). Now call almost any function ... like CreateHiddenKey
to create a hidden key (or try a number of other functions).
Here is an example that creates a hidden key, then puts some values in it (which are also hidden).
#define "NtRegistry.h"
void CMyApp::ReadRegistry()
{
CNtRegistry ntReg;
ntReg.InitNtRegistry();
if (ntReg.SetKey(HKEY_LOCAL_MACHINE,
_T("Software\\MyApp\\Settings"), FALSE, TRUE))
{
if (ntReg.CreateHiddenKey(_T("Software\\MyApp\\Settings\\Hidden")))
{
ntReg.WriteInt(_T("Data1"), 777);
ntReg.WriteFloat(_T("Pi"), 3.14159);
ntReg.WriteString(_T("UserName"), _T("DMadden61"));
int nData = ntReg.ReadInt(_T("Data1"), 0);
pi = ntReg.ReadFloat(_T("Pi"), 0.0);
CString csUserName = ntReg.ReadString(_T("UserName"), _T("ERR"));
}
}
else
{
TRACE("Failed to open/set key\n");
}
}
Simple enough? Well, it is, but how it is all put together was another thing.
Differences between Nt...() calls and Reg...() calls
I am going to show you some of the NT Native Registry APIs that CNtRegistry
uses (or at least, is ready for use), talk about what makes these different, and how I modified a popular function called "EnablePrivileges
" to use the NT Native APIs.
Some Native APIs |
Related Win32 APIs |
Required Privileges |
NtCreateKey |
RegCreateKey , RegCreateKeyEx |
N/A |
NtOpenKey |
RegOpenKey , RegOpenKeyEx |
N/A |
NtDeleteKey |
RegDeleteKey |
N/A |
NtFlushKey |
RegFlushKey |
N/A |
NtSetInformationKey |
None |
N/A |
NtQueryKey |
RegQueryInfoKey |
N/A |
NtEnumerateKey |
RegEnumerateKey , RegEnumerateKeyEx |
N/A |
NtNotifyChangeKey |
RegNotifyChangeKeyValue |
N/A |
NtDeleteValueKey |
RegDeleteValue |
N/A |
NtSetValueKey |
RegSetValue , RegSetValueEx |
N/A |
NtQueryValueKey |
RegQueryValue , RegQueryValueEx |
N/A |
NtEnumerateValueKey |
RegEnumValue |
N/A |
NtQueryMultipleValueKey |
RegQueryMultipleValues |
N/A |
NtEnumerateKey |
RegEnumKey , RegEnumKeyEx |
N/A |
*NtSaveKey |
RegSaveKey |
SeBackupPrivilege |
*NtRestoreKey |
RegRestoreKey |
SeRestorePrivilege |
*NtLoadKey |
RegLoadKey |
SeRestorePrivilege |
*NtLoadKey2 |
None |
SeRestorePrivilege |
*NtReplaceKey |
RegReplaceKey |
SeRestorePrivilege |
*NtUnloadKey |
RegUnloadKey |
SeRestorePrivilege |
NtClose |
CloseHandle |
N/A |
NtCreateFile |
CreateFile |
N/A |
NtOpenThread |
OpenThread |
N/A |
NtOpenProcessToken |
None |
SeCreateTokenPrivilege |
NtAdjustPrivilegesToken |
AdjustTokenPrivileges |
N/A |
NtQueryInformationToken |
GetTokenInformation |
N/A |
The parameters used for NT Native Registry APIs are not the same ones you are familiar with. Did you know that there is actually only two (2) Root (main) Keys in the registry? The rest are simply symbolic links. The two Root Keys are "\Registry\Machine (HKEY_LOCAL_MACHINE)" and "\Registry\User (HKEY_USERS)". Look below to see the HKEY and the TEXT equivalent. Where you would normally write a path for a subkey (RegCreateKey
) like this "SOFTWARE\MyApp" and also include the HKEY (HKEY_LOCAL_MACHINE), these Native APIs (NtCreateKey
) need the entire "full" path to the subkey, like this: "\Registry\Machine\SOFTWARE\MyApp". The CNtRegistry
class lets you make the call the way you are used to, but puts it all together for you (internally), simply by calling the two functions (see below) or one that does it both.
SetRootKey(HKEY_LOCAL_MACHINE);
SetKey(_T("SOFTWARE\\MyApp"),TRUE,TRUE);
- or just the one below which combines the two
SetKey(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\MyApp"),TRUE,TRUE);
HKEY_USERS \Registry\User
HKEY_CURRENT_USER \Registry\User\<Users_SID>
HKEY_LOCAL_MACHINE \Registry\Machine
HKEY_CLASSES_ROOT \Registry\Machine\SOFTWARE\Classes
HKEY_CURRENT_CONFIG \Registry\Machine\SYSTEM\CurrentControlSet\
Hardware Profiles\Current
Others are the UNICODE_STRING (U_S)
and OBJECT_ATTRIBUTES (O_A)
structures. The U_S
structure holds the "full" Key path (Unicode string) and length. The O_A
struct contains the properties. InitializeObjectAttributes(...)
initializes the O_A
structure that specifies the properties of an object handle to be opened. A pointer to this structure is then passed to the routine that actually opens the handle (e.g., NtOpenKey(...)
).
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
Code example using the above structures to create a registry key (the native way):
...
ANSI_STRING asName;
RtlZeroMemory(&asName,sizeof(asName));
RtlInitAnsiString(&asName,csName);
RtlZeroMemory(&usName,sizeof(usName));
RtlAnsiStringToUnicodeString(&usName,&asName,TRUE);
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
&usName,
OBJ_CASE_INSENSITIVE,
NULL,NULL);
HANDLE hKey = NULL;
m_NtStatus = NtCreateKey(&hKey,
KEY_ALL_ACCESS,
&ObjectAttributes,
0,
NULL,
REG_OPTION_NON_VOLATILE,
&m_dwDisposition);
if (!NT_SUCCESS(m_NtStatus)) {
return FALSE;
}
else {
NtClose(hKey);
}
...
Because I wanted to use some of the NT Registry Hive APIs (NtSaveKey
, etc...), I had to re-write the function EnablePrivileges
below to use NT Native APIs (except one function that I couldn't find a native call for, LookupPrivilegeValue
).
NTSTATUS CNtRegistry::EnablePrivilege(CString csPrivilege, BOOL bEnable)
{
TOKEN_PRIVILEGES NewState;
HANDLE hToken = NULL;
NTSTATUS NtStatus = STATUS_SUCCESS;
NtStatus = NtOpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|
TOKEN_QUERY|TOKEN_QUERY_SOURCE,
&hToken);
if (!NT_SUCCESS(NtStatus)) {
return NtStatus;
}
LUID luid;
if ( !LookupPrivilegeValue(NULL,
(LPCTSTR)csPrivilege,
&luid))
{
NtClose( hToken );
return (NTSTATUS) ERROR_FUNCTION_FAILED;
}
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Luid = luid;
NewState.Privileges[0].Attributes = (bEnable ? SE_PRIVILEGE_ENABLED : 0);
NtStatus = NtAdjustPrivilegesToken( hToken,
FALSE,
&NewState,
sizeof(NewState),
(PTOKEN_PRIVILEGES)NULL,
0);
NtClose(hToken);
return NtStatus;
}
In closing...
Being an "Intermediate" programmer, the problems I ran into were simply learning curves in the conversion of different types. I commented out the Hive functions because I was having some problems and I didn't want folks out there to screw up their systems. I also do not use all the Native Registry APIs in the nt.dll.
That is all I have got, I don't expect this to be perfect, so please give me your ideas to make it better. One thing to remember in life itself (and all the challenges that come with it) ... if you make a "mistake" and learn from it, then it never was a mistake to begin with...it was a "lesson"!
Thanks to ...
- CRegistry by Robert Pittenger. The inspiration behind this class!!
Things To-Do
- Incorporate the "Hive" functions in the code.
Ability to "Rename" Keys/Values (shouldn't be too hard with copying/deleting capabilities already there).
Take out "un-needed" code (used when I was writing it).
- Make sure everything is commented (working).
- There is a lot more...
History
There is a lot more that can be done to this class, but time is short and I thought that someone out there might like to help :-)
- August 10, 2006 (0.0.0.37)
- Added recursive parameter to the
CopyKey
function.
- Added "
DeleteKeyRecursive()
" function.
- July 16, 2006 (0.0.0.36)
- Added "
ShowPermissionsDlg()
" common dialog.
- Added Key path in the statusbar.
- July 2, 2006 (0.0.0.35)
- Changed the parameters of "
CopyKeys()/CopyValues()
" functions.
- This makes it easier to copy anything/anywhere.
- Changed the parameters of the "
FindHiddenKeys()
" function.
- This is so the output goes to a
CStringArray
(instead of a message box) which allowed me to display the output in a ListCtrl for display (thanks to a suggestion from "Tcpip2005" from CodeProject)!!
- Added the "
InitNtRegistry()
" function which does all the initialization.
- Added the
CaseSensitive
parameter to the "Search()
" function.
- Added "
#pragma comment(linker...)
" to stdafx.h to show XP themes.
- Added some "
Rtl...()
" string functions.
RtlInitString()
RtlInitAnsiString()
RtlInitUnicodeString()
RtlAnsiStringToUnicodeString()
RtlUnicodeStringToAnsiString()
RtlFreeString()
RtlFreeAnsiString()
RtlFreeUnicodeString()
- Jun 24, 2006 (0.0.0.34)
- Added "
RenameKey()
" that uses the "NtRenameKey()
".
- Added "
RenameValue()
" that uses home-bread functions.
- Reformated the header and source so that the order of the functions in the header match the order in the source.
- Jun 22, 2006 (0.0.0.33)
- Combined "
SetRootKey()
and SetKey
" and added more explanations in the article.
- Added "
GetCurrentUsersTextualSid()
" that returns the private variable "m_csSID
".
- Jun 11, 2006 - Release to public.
- Jun 03, 2004 - Initial playing.