Search code examples
c++winapiwdk

NtOpenKey fails with 0xC0000034 - how to fix this?


I'm creating a user-mode CMD app using VS 2013 in C++ and I'm trying to use the native registry editing functions in it. I'm trying to open certain key with 'NtOpenKey' but it always fails with 'STATUS_OBJECT_NAME_NOT_FOUND' and I'm sure that the 'object' is in it's place so the reason must be somewhere else. I want to use the native registry API's because they can handle 'Hidden Registry Keys' - look here for more info. Here is a snippet of my code:

#include <Windows.h>
#include <tchar.h>

#include <wininet.h> 

#include <iostream>

#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "Nt_Funcs_declr.h" //here I have manually included the native api declarations as normally I'm denied to use them but they're exported in ntdll.dll so basically it is possible

#include <zlib.h>

//Obitain Steam folder path

wchar_t *GetSteamPathBuffer()
{
    //Open the Sofware Steam registry

    OBJECT_ATTRIBUTES objAttrs;

    objAttrs.Length = sizeof(OBJECT_ATTRIBUTES);

    objAttrs.RootDirectory = NULL;

    wchar_t strRegSteam [] = L"\\Registry\\Machine\\SOFTWARE\\Valve";

    UNICODE_STRING uStrTmp = { sizeof(strRegSteam), sizeof(strRegSteam), strRegSteam };

    objAttrs.ObjectName = &uStrTmp;

    objAttrs.Attributes = OBJ_CASE_INSENSITIVE; // 

    objAttrs.SecurityDescriptor = NULL;

    objAttrs.SecurityQualityOfService = NULL;

    HANDLE pKey;

    ULONG tmmp = NtOpenKey(&pKey, GENERIC_READ, &objAttrs); //here it fails with 'STATUS_OBJECT_NAME_NOT_FOUND'
    if(tmmp)
    {
        cout << "Error: " << GetLastError();
        return NULL;
    }

//....
}

And Nt_Funcs_declr.h:

#pragma once


//NTDLL import declarations

#define STATUS_BUFFER_TOO_SMALL          ((NTSTATUS)0xC0000023L)

//
// Unicode strings are counted 16-bit character strings. If they are
// NULL terminated, Length does not include trailing NULL.
//

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
#ifdef MIDL_PASS
    [size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT * Buffer;
#else // MIDL_PASS
    _Field_size_bytes_part_(MaximumLength, Length) PWCH   Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;

//
// Valid values for the Attributes field
//

#define OBJ_INHERIT             0x00000002L
#define OBJ_PERMANENT           0x00000010L
#define OBJ_EXCLUSIVE           0x00000020L
#define OBJ_CASE_INSENSITIVE    0x00000040L
#define OBJ_OPENIF              0x00000080L
#define OBJ_OPENLINK            0x00000100L
#define OBJ_KERNEL_HANDLE       0x00000200L
#define OBJ_FORCE_ACCESS_CHECK  0x00000400L
#define OBJ_VALID_ATTRIBUTES    0x000007F2L

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;        // Points to type SECURITY_DESCRIPTOR
    PVOID SecurityQualityOfService;  // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
typedef CONST OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;


extern "C"
NTSYSAPI
NTSTATUS
NTAPI
NtOpenKey(
_Out_ PHANDLE KeyHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes
);

typedef enum _KEY_INFORMATION_CLASS {
    KeyBasicInformation,
    KeyNodeInformation,
    KeyFullInformation,
    KeyNameInformation,
    KeyCachedInformation,
    KeyFlagsInformation,
    KeyVirtualizationInformation,
    KeyHandleTagsInformation,
    KeyTrustInformation,
    MaxKeyInfoClass  // MaxKeyInfoClass should always be the last enum
} KEY_INFORMATION_CLASS;

extern "C"
NTSYSAPI
NTSTATUS
NTAPI
NtQueryKey(
_In_ HANDLE KeyHandle,
_In_ KEY_INFORMATION_CLASS KeyInformationClass,
_Out_writes_bytes_opt_(Length) PVOID KeyInformation,
_In_ ULONG Length,
_Out_ PULONG ResultLength
);

typedef enum _KEY_VALUE_INFORMATION_CLASS {
    KeyValueBasicInformation,
    KeyValueFullInformation,
    KeyValuePartialInformation,
    KeyValueFullInformationAlign64,
    KeyValuePartialInformationAlign64,
    MaxKeyValueInfoClass  // MaxKeyValueInfoClass should always be the last enum
} KEY_VALUE_INFORMATION_CLASS;

typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
    ULONG   TitleIndex;
    ULONG   Type;
    ULONG   DataLength;
    _Field_size_bytes_(DataLength) UCHAR Data[1]; // Variable size
} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;

extern "C"
NTSYSAPI
NTSTATUS
NTAPI
NtQueryValueKey(
_In_ HANDLE KeyHandle,
_In_ PUNICODE_STRING ValueName,
_In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
_Out_writes_bytes_opt_(Length) PVOID KeyValueInformation,
_In_ ULONG Length,
_Out_ PULONG ResultLength
);

NOTE: It's for educational prupose so please don't ask me why I don't use WIN32 API.


Solution

  • NB: using the kernel API from user mode is unsupported. I strongly recommend against doing so, unless there is a compelling reason why it is necessary.

    Here's the problem:

    UNICODE_STRING uStrTmp = { sizeof(strRegSteam), sizeof(strRegSteam), strRegSteam };
    

    From the documentation for UNICODE_STRING:

    If the string is null-terminated, Length does not include the trailing null character.

    So you should be saying something like

    UNICODE_STRING uStrTmp = { sizeof(strRegSteam) - sizeof(wchar_t), 
                               sizeof(strRegSteam), 
                               strRegSteam };
    

    As written, your code was attempting to open a key named L"Valve\0" instead of the key named L"Valve".


    Addendum: it has been disputed whether so-called "hidden" keys (an unfortunate name IMO; the keys are visible to Win32 code, they simply can't be manipulated) are actually possible, so here's working code to create one:

    #include <Windows.h>
    
    #include <stdio.h>
    
    typedef struct _UNICODE_STRING {
        USHORT Length;
        USHORT MaximumLength;
        PWCH   Buffer;
    } UNICODE_STRING;
    typedef UNICODE_STRING *PUNICODE_STRING;
    typedef const UNICODE_STRING *PCUNICODE_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;
    typedef CONST OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;
    
    #define OBJ_CASE_INSENSITIVE    0x00000040L
    
    #pragma comment(lib, "ntdll.lib")
    
    __declspec(dllimport) NTSTATUS NTAPI NtCreateKey(
        __out PHANDLE KeyHandle,
        __in ACCESS_MASK DesiredAccess,
        __in POBJECT_ATTRIBUTES ObjectAttributes,
        __reserved ULONG TitleIndex,
        __in_opt PUNICODE_STRING Class,
        __in ULONG CreateOptions,
        __out_opt PULONG Disposition
        );
    
    NTSYSAPI NTSTATUS NTAPI NtOpenKey(
        __out PHANDLE KeyHandle,
        __in ACCESS_MASK DesiredAccess,
        __in POBJECT_ATTRIBUTES ObjectAttributes
        );
    
    NTSYSAPI NTSTATUS NTAPI NtDeleteKey(
        __out HANDLE KeyHandle
        );
    
    int main(int argc, char ** argv)
    {
        HANDLE pKey;
        NTSTATUS result;
    
        OBJECT_ATTRIBUTES objAttrs;
        wchar_t strSoftwareKey [] = L"\\Registry\\Machine\\SOFTWARE\\Test\0Key";
    
    // If you use this string instead, the key functions normally, proving that the
    // issue isn't because we're using UTF-16 rather than ANSI strings:
    //
    // wchar_t strSoftwareKey [] = L"\\Registry\\Machine\\SOFTWARE\\Test\u2D80Key";
    
        UNICODE_STRING uStrSoftwareKey = { 
            sizeof(strSoftwareKey) - sizeof(wchar_t), 
            sizeof(strSoftwareKey), 
            strSoftwareKey };
    
        objAttrs.Length = sizeof(OBJECT_ATTRIBUTES);
        objAttrs.RootDirectory = NULL;
        objAttrs.ObjectName = &uStrSoftwareKey;
        objAttrs.Attributes = OBJ_CASE_INSENSITIVE;
        objAttrs.SecurityDescriptor = NULL;
        objAttrs.SecurityQualityOfService = NULL;
    
        result = NtCreateKey(&pKey, GENERIC_ALL, &objAttrs, 0, NULL, 0, NULL);
        if(result)
        {
            printf("NtCreateKey: %x\n", result);
            return NULL;
        }
    
    #if 0  // enable this section to delete the key
           // you won't be able to use regedit!
        result = NtDeleteKey(pKey);
        if(result)
        {
            printf("NtDeleteKey: %x\n", result);
            return NULL;
        }
    #endif
    }
    

    As of Windows 7, at least, this still works. (You'll need a copy of ntdll.lib, available from the DDK/WDK, in order to build this code.)

    Please do not do this in production code or on other people's machines.