I have a password filter dll which, for now at least, is simply going to write to a log the username and the password when a user account's password is successfully changed. I'm coming up to some age-old problems with c-strings.
The two variables I'm interested in are of the type PUNICODE_STRING (which is simply a *UNICODE_STRING). Naturally I search the web for this data type which landed me here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa380518(v=vs.85).aspx
Give that information I know that I can access the length of this animal and that it has a pointer to a wide-character string. Awesome. That is plenty of knowledge to extract the contents I'll need (so I thought).
Here is my dll code (dllmain.cpp)
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include <iostream>
#define LOGFILE "c:\\PasswordFilter.txt"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void WriteToLog (const wchar_t* UserName, const wchar_t* NewPassword)
{
#ifdef LOGFILE
FILE* log = fopen(LOGFILE, "a");
if(NULL == log)
{
return;
}
fwprintf(log,L"%s password was successfully reset to %s\r\n",UserName,NewPassword);
fclose(log);
#endif
return;
}
LPWSTR __stdcall ConvertWideChar(PUNICODE_STRING input)
{
wchar_t* wszString = new wchar_t[input->Length + 1];
memset(wszString, 0, sizeof(wchar_t) * (input->Length + 1));
wcsncpy(wszString,input->Buffer,input->Length);
#ifdef LOGFILE
FILE* log = fopen(LOGFILE, "a");
if(NULL != log)
{
fwprintf(log,L"value of wszString: %s\r\n",wszString);
fclose(log);
}
#endif
return wszString;
}
BOOLEAN __stdcall InitializeChangeNotify(void)
{
return TRUE;
}
NTSTATUS __stdcall PasswordChangeNotify(
PUNICODE_STRING UserName,
ULONG RelativeId,
PUNICODE_STRING NewPassword
)
{
LPWSTR ConvertedUserName = ConvertWideChar(UserName);
LPWSTR ConvertedNewPassword = ConvertWideChar(NewPassword);
WriteToLog(ConvertedUserName,ConvertedNewPassword);
return 0;
}
BOOLEAN __stdcall PasswordFilter(
PUNICODE_STRING AccountName,
PUNICODE_STRING FullName,
PUNICODE_STRING Password,
BOOLEAN SetOperation
)
{
return TRUE;
}
And here is the contents of the header file (stdafx.h)
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <stdio.h>
#include <windows.h>
#include <winnt.h>
#include <NTSecAPI.h>
// TODO: reference additional headers your program requires here
Naturally my concern is to retain accuracy. And this is where my pain begins. We have a wchar_t which communicated to me that this is likely in unicode with a UTF16 format (like all other windows based strings). The code above copies the PUNICODE_STRING's buffer to a zero-terminated wchar_t (since the buffer isn't guaranteed to be zero terminated itself) and then attempts to output that string to a log file.
I first attempted to use WideCharToMultiByte as a way to copy the buffer to a character string but that never seemed to work. The result would be null each and every time. This also ran the issue of translating unicode values to a simpler format which runs the risk of data loss. So I decided to go with what I have there for now, but the logged values have ?'s and other garbage after them, which is likely a unicode problem.
I would REALLY like to retain the unicode encoding type of this data structure if possible. How can I do so and print my values to a log?
You are not using the UNICODE_STRING::Length field correctly. It is expressed in bytes, not in characters. So you are not allocating+filling your null-terminated buffers correctly. You are also leaking the buffers you allocate.
You don't need to allocate the buffers at all. You can pass the original Buffer and Length values directly to fwprintf, no null terminators needed:
NTSTATUS __stdcall PasswordChangeNotify(
PUNICODE_STRING UserName,
ULONG RelativeId,
PUNICODE_STRING NewPassword
)
{
#ifdef LOGFILE
FILE* log = fopen(LOGFILE, "a");
if (NULL != log)
{
fwprintf(log, L"%.*s password was successfully reset to %.*s\r\n",
UserName->Length / sizeof(WCHAR), UserName->Buffer,
NewPassword->Length / sizeof(WCHAR), NewPassword->Buffer);
fclose(log);
}
#endif
return 0;
}
If you really need to add null terminators, use std::wstring instead of allocating+copying manually:
#include <string>
NTSTATUS __stdcall PasswordChangeNotify(
PUNICODE_STRING UserName,
ULONG RelativeId,
PUNICODE_STRING NewPassword
)
{
#ifdef LOGFILE
FILE* log = fopen(LOGFILE, "a");
if (NULL != log)
{
fwprintf(log, L"%.s password was successfully reset to %.s\r\n",
std::wstring(UserName->Buffer, UserName->Length / sizeof(WCHAR)).c_str(),
std::wstring(NewPassword->Buffer, NewPassword->Length / sizeof(WCHAR)).c_str());
fclose(log);
}
#endif
return 0;
}
You can actually print a UNICODE_STRING structure using the modifier %wZ with printf. No need for any fancy Buffer translations or worrying about null terminators.
UNICODE_STRING someStr;
RtlInitUnicodeString(&someStr, L"Hello");
printf("String: %wZ\n", &someStr);
fini.
Related
I know that there are lots of questions like this as it is a common error, I also know it is happening because I am using unicode. However, after reading through SO/microsoft docs and fixing the code, it still does not seem to work and I am stumped.
The code is as follows:
#include <Windows.h>
#include <Tlhelp32.h>
#include <string>
DWORD find_pid(const char* procname) {
// Dynamically resolve some functions
HMODULE kernel32 = GetModuleHandleA("Kernel32.dll");
using CreateToolhelp32SnapshotPrototype = HANDLE(WINAPI *)(DWORD, DWORD);
CreateToolhelp32SnapshotPrototype CreateToolhelp32Snapshot = (CreateToolhelp32SnapshotPrototype)GetProcAddress(kernel32, "CreateToolhelp32Snapshot");
using Process32FirstPrototype = BOOL(WINAPI *)(HANDLE, LPPROCESSENTRY32);
Process32FirstPrototype Process32First = (Process32FirstPrototype)GetProcAddress(kernel32, "Process32First");
using Process32NextPrototype = BOOL(WINAPI *)(HANDLE, LPPROCESSENTRY32);
Process32NextPrototype Process32Next = (Process32NextPrototype)GetProcAddress(kernel32, "Process32Next");
// Init some important local variables
HANDLE hProcSnap;
PROCESSENTRY32 pe32;
DWORD pid = 0;
pe32.dwSize = sizeof(PROCESSENTRY32);
// Find the PID now by enumerating a snapshot of all the running processes
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap)
return 0;
if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}
while (Process32Next(hProcSnap, &pe32)) {
//--->here is offending code...
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
}
// Cleanup
CloseHandle(hProcSnap);
return pid;
}
I changed the code to this:
if (strcmp(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
but still get the same error. Any ideas?
PROCESSENTRY32 uses wchars when you define UNICODE. The doc's are probably misleading.
Tlhelp32.h defines this:
#ifdef UNICODE
#define Process32First Process32FirstW
#define Process32Next Process32NextW
#define PROCESSENTRY32 PROCESSENTRY32W
#define PPROCESSENTRY32 PPROCESSENTRY32W
#define LPPROCESSENTRY32 LPPROCESSENTRY32W
#endif // !UNICODE
As you can see, PROCESSENTRY32 maps to PROCESSENTRY32W if UNICODE is defined.
pe32.szExeFile is a WCHAR szExeFile[MAX_PATH]; // Path
That is the reason why strcmp fails with the error you get. strcmp expects a char* but you pass pe32.szExeFile, which in your case is a WCHAR*.
Take real care to get the correct pointers to the helper functions. Are you getting the ANSI or UNICODE pointers? You are using a lot of C-style casts in the GetProcAddress calls. You can easily end up casting the return value to an ANSI style function, but using the UNICODE style pointer type.
I am developing a kernel mode filter driver, I want this driver to send a UNICODE String to an exe running in user mode. Kindly provide an example for this, as i am a beginner in driver development.
Below is the code of my driver (From where I want to send UNICODE string)
#include "drv_common.h"
#include "ntddk.h"
#include "FsFilter.h"
#define SOME_SIZE
// PassThrough IRP Handler
NTSTATUS FsFilterDispatchPassThrough( __in PDEVICE_OBJECT DeviceObject, __in PIRP Irp )
{
PFSFILTER_DEVICE_EXTENSION pDevExt = (PFSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(pDevExt->AttachedToDeviceObject, Irp);
}
///////////////////////// struct file info ////////////////////////////////////
struct {
OBJECT_NAME_INFORMATION NameInfo;
WCHAR Buffer[64]; // 64 chars must be enough for everybody :)
} InfoBuffer;
///////////////////////////////////////////////////////////////////////////////////////////////////
// IRP_MJ_CREATE IRP Handler
NTSTATUS FsFilterDispatchCreate(
__in PDEVICE_OBJECT DeviceObject,
__in PIRP Irp
)
{
PFILE_OBJECT pFileObject = IoGetCurrentIrpStackLocation(Irp)->FileObject;
PUNICODE_STRING **temp**;
RtlInitUnicodeString( temp, L"\\vs\\vs\\Setup\\eula.txt" );
LONG flag = RtlCompareUnicodeString( temp, &pFileObject->FileName, TRUE );
if ( flag == 0 )
{
DbgPrint("File is opened.\n" );
return STATUS_UNSUCCESSFUL;
}
return FsFilterDispatchPassThrough(DeviceObject, Irp);
}
I want to send &pFileObject->FileName (UNICODE String) from the above code to an executable in the user mode.
Suppose, that executable will just print this string on console.
Below is my exe code in user mode
.......
.......
int main()
{
cout<< getUnicodeStringFromKernel(); // Just supposition
return 0;
}
There are a few different ways that you can "access" a kernel mode driver. The most obvious in this case would be to use the ioctl interface.
Unfortunately, I can't provide you with an example, because to achieve that would require me to install the Windows DDK on my virtual machine, along with actually writing the code for it.
There is, however, an article here which explains how ioctls in filter drivers work.
From your application, you need to use DeviceIoControl.
I am currently accessing the Windows Netapi32 using Netapi32.lib, currently I am using c++ to access the api. I am having trouble retrieving the computer name, currently one the NetFileEnum here on FILE_INFO_3 structure here. In the documentation it says,
fi3_username
Pointer to a string that specifies which user (on servers that have user-level security) or which computer (on servers that have
share-level security) opened the resource. Note that Windows does not
support share-level security.This string is Unicode if _WIN32_WINNT or FORCE_UNICODE are defined.
Now, the network that I am running this script in has indeed Share-level security, I am just not sure how to list the computer name.
Relevant code
Library includes:
#pragma comment(lib, "Netapi32.lib")
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <lm.h>
Initiate structure:
fstatus is defined in my code as, NET_API_STATUS fStatus, it is the I/O structure. Documentation here
if fstatus is successful the Return value NERR_Success.
If the function fails, the return value can be one of the following error codes.
ERROR_ACCESS_DENIED The user does not have access to the requested
information.
ERROR_INVALID_LEVEL The value specified for the level parameter is
not valid.
ERROR_INVALID_PARAMETER The specified parameter is not valid.
ERROR_MORE_DATA More entries are available. Specify a large enough buffer to receive all entries.
....
more here
To handle that I use if ((fStatus == NERR_Success) || (fStatus == ERROR_MORE_DATA))
The user name could not be found.
fStatus = NetFileEnum(
flServerName, //Pointer to a string that specifies the DNS or NetBIOS name of the remote server on which the function is to execute. If this parameter is NULL, the local computer is used.
flBasePath, //Pointer to a string that specifies a qualifier for the returned information. If this parameter is NULL, all open resources are enumerated.
flUserName, //Pointer to a string that specifies the name of the user or the name of the connection.
dfLevel, //Pointer to a string that specifies the name of the user or the name of the connection. Can be either 2 or 3, I am using 3
(LPBYTE*)&pFile, //Pointer to the address of the buffer that receives the information. The format of this data depends on the value of the level parameter.
fwPrefMaxLen, //pecifies the preferred maximum length of returned data, in bytes.
&fwEntriesRead, //Pointer to a value that receives the count of elements actually enumerated.
&fwTotalEntries, //Pointer to a value that receives the total number of entries that could have been enumerated from the current resume position.
&fwResumeHandle); //Pointer to a value that contains a resume handle which is used to continue an existing file search.
NET_API_STATUS NetFileEnum(
_In_ LMSTR servername,
_In_ LMSTR basepath,
_In_ LMSTR username,
_In_ DWORD level,
_Out_ LPBYTE *bufptr,
_In_ DWORD prefmaxlen,
_Out_ LPDWORD entriesread,
_Out_ LPDWORD totalentries,
_Inout_ PDWORD_PTR resume_handle
);
RAW Values for the above:
NET_API_STATUS fStatus;
LPFILE_INFO_3 pFile = NULL;
LPFILE_INFO_3 pTmpFile;
DWORD dfLevel = 3;
LPTSTR flServerName = NULL;
LPTSTR flUserName = NULL;
LPTSTR flBasePath = NULL;
DWORD fwPrefMaxLen = MAX_PREFERRED_LENGTH;
DWORD fwEntriesRead = 0;
DWORD fwTotalEntries = 0;
DWORD fwResumeHandle = 0;
pTmpfile is the level 3 (documentation here) buffer object,
bufptr [out]
Pointer to the address of the buffer that receives the information. The format of this data depends on the value of the levelparameter.
This buffer returns data in this format,
typedef struct _FILE_INFO_3 {
DWORD fi3_id;
DWORD fi3_permissions;
DWORD fi3_num_locks;
LMSTR fi3_pathname;
LMSTR fi3_username;
} FILE_INFO_3, *PFILE_INFO_3, *LPFILE_INFO_3;
Retrieve data:
printf("\n\tComputer: %S\n", pTmpFile->fi3_username); //how do I retrieve computer name???
printf("\n\tid: %D\n", pTmpFile->fi3_id);
printf("\n\tpath: %S\n", pTmpFile->fi3_pathname);
**It is important to note that I have tried this using vbnet and it works, but somehow cant figure it how to do it on c++.
Complete Test Program:
#ifndef UNICODE
#define UNICODE
#endif
//Initialize the NetAPI Library
#pragma comment(lib, "Netapi32.lib")
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <lm.h>
int wmain(int argc, wchar_t *argv[])
{
//NetFile Enum, using 3 Level.
NET_API_STATUS fStatus;
LPFILE_INFO_3 pFile = NULL;
LPFILE_INFO_3 pTmpFile;
DWORD dfLevel = 3;
LPTSTR flServerName = NULL;
LPTSTR flUserName = NULL;
LPTSTR flBasePath = NULL;
DWORD fwPrefMaxLen = MAX_PREFERRED_LENGTH;
DWORD fwEntriesRead = 0;
DWORD fwTotalEntries = 0;
DWORD fwResumeHandle = 0;
DWORD fi;
//
// Check command line arguments.
// Dont need this currently.
//
do
{
fStatus = NetFileEnum(flServerName,
flBasePath,
flUserName,
dfLevel,
(LPBYTE*)&pFile,
fwPrefMaxLen,
&fwEntriesRead,
&fwTotalEntries,
&fwResumeHandle);
if ((fStatus == NERR_Success) || (fStatus == ERROR_MORE_DATA))
{
if ((pTmpFile = pFile) != NULL)
{
for (fi=0; fi < fwEntriesRead; fi++)
{
assert(pTmpFile != NULL);
if (pTmpFile == NULL)
{
fprintf(stderr, "An access violation has occurred\n");
break;
}
printf("\n\tComputer: %S", pTmpFile->fi3_username);
printf("\n\tid: %d", pTmpFile->fi3_id);
printf("\n\tpath: %s", pTmpFile->fi3_pathname);
printf("\n\tLocks: %d\n", pTmpFile->fi3_num_locks);
pTmpFile++;
fwTotalEntries++;
}
}
}
else
fprintf(stderr, "A system error has occurred: %d\n", fStatus);
//
// Free the allocated memory.
//
if (pFile != NULL)
{
NetApiBufferFree(pFile);
pFile = NULL;
}
}
//
// Continue to call NetFilEnum while
// there are more entries.
//
while (fStatus == ERROR_MORE_DATA);
if (pFile != NULL)
NetApiBufferFree(pFile);
return 0;
}
Output:
From Build:
1>------ Build started: Project: Perfmon, Configuration: Release Win32 ------
1>Compiling...
1>file_enumerate.cpp
1>Linking...
1>Generating code
1>Finished generating code
1>Embedding manifest...
1>Build log was saved at "file://...."
1>Perfmon - 0 error(s), 0 warning(s)
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
Run:
Computer: User1 //prints only username, not computername (in our system, each user has the same username)
id: 1005687
path: c:\fips\library
Computer: User2 //prints only username, not computername (in our system, each user has the same username)
id: 1005689
path: c:\fips\library\util
If anyone else is wondering about the solution, I figured it out. To query the number of files assoicated with the Computer and not just the User, the NetFileEnum function has to be used, documentation here. The NetFileEnum syntax is shown below,
NET_API_STATUS NetFileEnum(
_In_ LMSTR servername,
_In_ LMSTR basepath,
_In_ LMSTR username,
_In_ DWORD level,
_Out_ LPBYTE *bufptr,
_In_ DWORD prefmaxlen,
_Out_ LPDWORD entriesread,
_Out_ LPDWORD totalentries,
_Inout_ PDWORD_PTR resume_handle
);
Where you have to pass the Computer Name as LMSTR username (you can retrieve the computer name by quering NetSessionEnum(502) which will return all computer names in the network, documentation here ) and the query returns the file details based on the DWORD level, either FILE_INFO_3 documentation here and FILE_INFO_2 documentation here.
I try to implement password filter, so I write a simple password filter.
I followed the document in the MSDN, and make sure that the functions are declared correctly.
I compile in VS 2010.
.def file:
LIBRARY myFilt
EXPORTS
InitializeChangeNotify
PasswordFilter
PasswordChangeNotify
.cpp file:
#include <windows.h>
#include <stdio.h>
#include <ntsecapi.h>
void writeToLog(const char* szString)
{
FILE* pFile = fopen("c:\\work\\logFile.txt", "a+");
if (NULL == pFile)
{
return;
}
fprintf(pFile, "%s\r\n", szString);
fclose(pFile);
return;
}
// Default DllMain implementation
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
OutputDebugString(L"DllMain");
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
BOOLEAN __stdcall InitializeChangeNotify(void)
{
OutputDebugString(L"InitializeChangeNotify");
writeToLog("InitializeChangeNotify()");
return TRUE;
}
BOOLEAN __stdcall PasswordFilter(
PUNICODE_STRING AccountName,
PUNICODE_STRING FullName,
PUNICODE_STRING Password,
BOOLEAN SetOperation
)
{
OutputDebugString(L"PasswordFilter");
return TRUE;
}
NTSTATUS __stdcall PasswordChangeNotify(
PUNICODE_STRING UserName,
ULONG RelativeId,
PUNICODE_STRING NewPassword
)
{
OutputDebugString(L"PasswordChangeNotify");
writeToLog("PasswordChangeNotify()");
return 0;
}
I put myFilt.dll in %windir%\system32, add "myFilt" to "Notification Packages" in the registry, restart the computer, change the password, and nothing happens.
I opened depends.exe and saw that the functions are correctly:
InitializeChangeNotify
PasswordChangeNotify
PasswordFilter
Where is the mistake??
Thanks.
I found the problem! I changed the runtime library from Multi-threaded Debug DLL (/MDd) to Multi-threaded Debug (/MTd) and it works perfect! :)
– user1375970 May 5 at 10:38
Notification Packages
Specifies the dynamic-link libraries (DLLs) that are loaded or called when passwords are set or changed. To specify more than one file, list the file names one above the other by pressing ENTER between each file name.
above the other!
Given a handle to a Windows Registry Key, such as the ones that are set by ::RegOpenKeyEx(), is it possible to determine the full path to that key?
I realize that in a simple application all you have to do is look up 5 or 10 lines and read... but in a complex app like the one I'm debugging, the key I'm interested in can be opened from a series of calls.
Use LoadLibrary and NtQueryKey exported function as in the following code snippet.
#include <windows.h>
#include <string>
typedef LONG NTSTATUS;
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
#ifndef STATUS_BUFFER_TOO_SMALL
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
#endif
std::wstring GetKeyPathFromKKEY(HKEY key)
{
std::wstring keyPath;
if (key != NULL)
{
HMODULE dll = LoadLibrary(L"ntdll.dll");
if (dll != NULL) {
typedef DWORD (__stdcall *NtQueryKeyType)(
HANDLE KeyHandle,
int KeyInformationClass,
PVOID KeyInformation,
ULONG Length,
PULONG ResultLength);
NtQueryKeyType func = reinterpret_cast<NtQueryKeyType>(::GetProcAddress(dll, "NtQueryKey"));
if (func != NULL) {
DWORD size = 0;
DWORD result = 0;
result = func(key, 3, 0, 0, &size);
if (result == STATUS_BUFFER_TOO_SMALL)
{
size = size + 2;
wchar_t* buffer = new (std::nothrow) wchar_t[size/sizeof(wchar_t)]; // size is in bytes
if (buffer != NULL)
{
result = func(key, 3, buffer, size, &size);
if (result == STATUS_SUCCESS)
{
buffer[size / sizeof(wchar_t)] = L'\0';
keyPath = std::wstring(buffer + 2);
}
delete[] buffer;
}
}
}
FreeLibrary(dll);
}
}
return keyPath;
}
int _tmain(int argc, _TCHAR* argv[])
{
HKEY key = NULL;
LONG ret = ERROR_SUCCESS;
ret = RegOpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft", &key);
if (ret == ERROR_SUCCESS)
{
wprintf_s(L"Key path for %p is '%s'.", key, GetKeyPathFromKKEY(key).c_str());
RegCloseKey(key);
}
return 0;
}
This will print the key path on the console:
Key path for 00000FDC is
'\REGISTRY\MACHINE\SOFTWARE\Microsoft'.
I was excited to find this article and its well liked solution.
Until I found that my system's NTDLL.DLL did not have NtQueryKeyType.
After some hunting around, I ran across ZwQueryKey in the DDK forums.
It is in C#, but here is the solution that works for me:
enum KEY_INFORMATION_CLASS
{
KeyBasicInformation, // A KEY_BASIC_INFORMATION structure is supplied.
KeyNodeInformation, // A KEY_NODE_INFORMATION structure is supplied.
KeyFullInformation, // A KEY_FULL_INFORMATION structure is supplied.
KeyNameInformation, // A KEY_NAME_INFORMATION structure is supplied.
KeyCachedInformation, // A KEY_CACHED_INFORMATION structure is supplied.
KeyFlagsInformation, // Reserved for system use.
KeyVirtualizationInformation, // A KEY_VIRTUALIZATION_INFORMATION structure is supplied.
KeyHandleTagsInformation, // Reserved for system use.
MaxKeyInfoClass // The maximum value in this enumeration type.
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_NAME_INFORMATION
{
public UInt32 NameLength; // The size, in bytes, of the key name string in the Name array.
public char[] Name; // An array of wide characters that contains the name of the key.
// This character string is not null-terminated.
// Only the first element in this array is included in the
// KEY_NAME_INFORMATION structure definition.
// The storage for the remaining elements in the array immediately
// follows this element.
}
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int ZwQueryKey(IntPtr hKey, KEY_INFORMATION_CLASS KeyInformationClass, IntPtr lpKeyInformation, int Length, out int ResultLength);
public static String GetHKeyName(IntPtr hKey)
{
String result = String.Empty;
IntPtr pKNI = IntPtr.Zero;
int needed = 0;
int status = ZwQueryKey(hKey, KEY_INFORMATION_CLASS.KeyNameInformation, IntPtr.Zero, 0, out needed);
if ((UInt32)status == 0xC0000023) // STATUS_BUFFER_TOO_SMALL
{
pKNI = Marshal.AllocHGlobal(sizeof(UInt32) + needed + 4 /*paranoia*/);
status = ZwQueryKey(hKey, KEY_INFORMATION_CLASS.KeyNameInformation, pKNI, needed, out needed);
if (status == 0) // STATUS_SUCCESS
{
char[] bytes = new char[2 + needed + 2];
Marshal.Copy(pKNI, bytes, 0, needed);
// startIndex == 2 skips the NameLength field of the structure (2 chars == 4 bytes)
// needed/2 reduces value from bytes to chars
// needed/2 - 2 reduces length to not include the NameLength
result = new String(bytes, 2, (needed/2)-2);
}
}
Marshal.FreeHGlobal(pKNI);
return result;
}
I've only ever tried it while running as Administrator, which may be required.
The result is a bit oddly formatted: \REGISTRY\MACHINE\SOFTWARE\company\product for example, instead of HKEY_LOCAL_MACHINE\SOFTWARE\company\product.
Nominally no because it's just a handle and there is no API that I know of to let you do this in the normal Windows API's.
HOWEVER the Native API has lots of functions some of which can give you handles open for given files and the like so there maybe something similar for the Registry. That and RegMon by SysInternals may do something like this but you'll have to Google I'm afraid :/
You can use RegSaveKey and write it to a file, then look at the file.
Alternatively you can keep a global map of HKEYs to LPCWSTRs and add entries when you open them and do lookups whenever.
You may also be able to do something with the !reg command in WinDBG / NTSD, but you can't just give it the HKEY. You'll have to do some other trickery to get the info you want out of it.
Since std::wstring allows to construct string from pointer and count of characters, and the kernel string always return the count of bytes, it is not necessary to terminated the string with NUL. I do not suggest that to add size or to offset the pointer by constant number directly, it's better to use the real data type like the structure types instead, and std::vector<UCHAR> instead of new for dynamic memory allocating. I modified the code from highly upvoted answer as the followings.
The legacy way, obtaining the function pointer from ntdll.dll dynamically:
#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>
#include <winternl.h>
#include <string>
#include <vector>
#define REG_KEY_PATH_LENGTH 1024
typedef enum _KEY_INFORMATION_CLASS {
KeyBasicInformation,
KeyNodeInformation,
KeyFullInformation,
KeyNameInformation,
KeyCachedInformation,
KeyFlagsInformation,
KeyVirtualizationInformation,
KeyHandleTagsInformation,
KeyTrustInformation,
KeyLayerInformation,
MaxKeyInfoClass
} KEY_INFORMATION_CLASS;
typedef struct _KEY_NAME_INFORMATION {
ULONG NameLength;
WCHAR Name[1];
} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION;
typedef NTSTATUS (NTAPI *PFN_NtQueryKey)(
__in HANDLE /* KeyHandle */,
__in KEY_INFORMATION_CLASS /* KeyInformationClass */,
__out_opt PVOID /* KeyInformation */,
__in ULONG /* Length */,
__out ULONG * /* ResultLength */
);
std::wstring RegQueryKeyPath(HKEY hKey)
{
std::wstring keyPath;
if (hKey != NULL)
{
HMODULE hinstDLL = GetModuleHandleW(L"ntdll.dll");
if (hinstDLL != NULL)
{
FARPROC pfn = GetProcAddress(hinstDLL, "NtQueryKey");
if (pfn != NULL)
{
NTSTATUS Status;
std::vector<UCHAR> Buffer(FIELD_OFFSET(KEY_NAME_INFORMATION, Name) + sizeof(WCHAR) * REG_KEY_PATH_LENGTH);
KEY_NAME_INFORMATION *pkni;
ULONG Length;
TryAgain:
Status = reinterpret_cast<PFN_NtQueryKey>(pfn)(hKey, KeyNameInformation, Buffer.data(), Buffer.size(), &Length);
switch (Status) {
case STATUS_BUFFER_TOO_SMALL:
case STATUS_BUFFER_OVERFLOW:
Buffer.resize(Length);
goto TryAgain;
case STATUS_SUCCESS:
pkni = reinterpret_cast<KEY_NAME_INFORMATION *>(Buffer.data());
keyPath.assign(pkni->Name, pkni->NameLength / sizeof(WCHAR));
default:
break;
}
}
}
}
return keyPath;
}
If you are using Visual Studio 2015 or above, ntdll.lib is included by default, so I suggest that linking to ntdll.dll statically:
#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll")
#include <string>
#include <vector>
#define REG_KEY_PATH_LENGTH 1024
typedef enum _KEY_INFORMATION_CLASS {
KeyBasicInformation,
KeyNodeInformation,
KeyFullInformation,
KeyNameInformation,
KeyCachedInformation,
KeyFlagsInformation,
KeyVirtualizationInformation,
KeyHandleTagsInformation,
KeyTrustInformation,
KeyLayerInformation,
MaxKeyInfoClass
} KEY_INFORMATION_CLASS;
typedef struct _KEY_NAME_INFORMATION {
ULONG NameLength;
WCHAR Name[1];
} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION;
EXTERN_C NTSYSAPI NTSTATUS NTAPI NtQueryKey(
__in HANDLE /* KeyHandle */,
__in KEY_INFORMATION_CLASS /* KeyInformationClass */,
__out_opt PVOID /* KeyInformation */,
__in ULONG /* Length */,
__out ULONG * /* ResultLength */
);
std::wstring RegQueryKeyPath(HKEY hKey)
{
std::wstring keyPath;
NTSTATUS Status;
std::vector<UCHAR> Buffer(FIELD_OFFSET(KEY_NAME_INFORMATION, Name) + sizeof(WCHAR) * REG_KEY_PATH_LENGTH);
KEY_NAME_INFORMATION *pkni;
ULONG Length;
TryAgain:
Status = NtQueryKey(hKey, KeyNameInformation, Buffer.data(), Buffer.size(), &Length);
switch (Status) {
case STATUS_BUFFER_TOO_SMALL:
case STATUS_BUFFER_OVERFLOW:
Buffer.resize(Length);
goto TryAgain;
case STATUS_SUCCESS:
pkni = reinterpret_cast<KEY_NAME_INFORMATION *>(Buffer.data());
keyPath.assign(pkni->Name, pkni->NameLength / sizeof(WCHAR));
default:
break;
}
return keyPath;
}
Note that NtQueryKey returned STATUS_BUFFER_OVERFLOW but not STATUS_BUFFER_TOO_SMALL on Windows 10 if the supplied buffer is insufficient.
For ntsd/windbg:
!handle yourhandle 4