[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f9JpQ7HnGTBXdRfsBSnLf6GI5muygxQM-hP8as04d1aY":3},{"id":4,"question":5,"answer":6,"answerHtml":7,"slug":8,"keywords":9,"article":10,"status":34,"aiModel":39,"aiConfidence":39,"updatedAt":52,"createdAt":52,"_status":51},573,"How does placing a null byte (\\0) at the beginning of a registry value name help hide it?","When a registry value name starts with a null byte (\\0), Win32 API functions like `RegQueryValueEx` interpret that byte as a string terminator, causing them to read an empty name and fail with `ERROR_FILE_NOT_FOUND`. However, using Native API functions such as `NtCreateKey` allows specifying the exact string length, bypassing this truncation. This technique is detailed in the original article [Penetration Techniques - Creating \"Hidden\" Registry Entries](\u002Fnews\u002Fpenetration-techniques-creating-hidden-registry-entries).","\u003Cp>When a registry value name starts with a null byte (\\0), Win32 API functions like `RegQueryValueEx` interpret that byte as a string terminator, causing them to read an empty name and fail with `ERROR_FILE_NOT_FOUND`. However, using Native API functions such as `NtCreateKey` allows specifying the exact string length, bypassing this truncation. This technique is detailed in the original article [Penetration Techniques - Creating &quot;Hidden&quot; Registry Entries](\u002Fnews\u002Fpenetration-techniques-creating-hidden-registry-entries).\u003C\u002Fp>\u003Cp>\u003Ca href=\"\u002Fnews\u002Fpenetration-techniques-further-testing-on-hidden-registry\">Read the related One Day Sec article\u003C\u002Fa>\u003C\u002Fp>","how-does-placing-a-null-byte-0-at-the-beginning-of-a-registry-value-name-help-hi-1777483025676","null byte, registry hiding, Win32 API, Native API, NtCreateKey, string truncation",{"id":11,"title":12,"slug":13,"description":14,"content":15,"contentHtml":30,"cover":31,"author":32,"views":19,"readingTime":33,"status":34,"publishedAt":35,"seo":36,"tags":41,"qaPairs":42,"meta":48,"updatedAt":49,"createdAt":50,"_status":51},141,"Penetration Techniques - Further Testing on \"Hidden\" Registry","penetration-techniques-further-testing-on-hidden-registry","Advanced penetration testing on hidden registry techniques, exploring stealthy methods using Native APIs and zero-day exploits for cybersecurity defense.",{"root":16},{"type":17,"format":18,"indent":19,"version":20,"children":21,"direction":29},"root","",0,1,[22],{"type":23,"format":18,"indent":19,"version":20,"children":24,"direction":29},"paragraph",[25],{"mode":26,"text":27,"type":28,"style":18,"detail":19,"format":19,"version":20},"normal","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The previous article 'Penetration Techniques - Creation of \"Hidden\" Registry' introduced the registry hiding technique used by Poweliks, analyzed its principles, and implemented the functionality through a C program.\u003C\u002Fp>\u003Cp>This article will conduct further testing and share a more \"stealthy\" method (this method has not been found in public materials yet, pending confirmation).\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Errors when reading with Win32 API\u003C\u002Fli>\u003Cli>The case of placing \"\\0\" in the middle of a string\u003C\u002Fli>\u003Cli>Application of other Native APIs (such as NtCreateFile)\u003C\u002Fli>\u003Cli>More covert exploitation methods\u003C\u002Fli>\u003Cli>Defense and detection\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Hiding Principle\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>For Windows systems, \"\\0\" (i.e., 0x0000) is recognized as the string terminator.\u003C\u002Fp>\u003Cp>Therefore, during the reading of such a string, encountering a leading \"\\0\" will be interpreted as the terminator, causing premature truncation and leading to a read error.\u003C\u002Fp>\u003Cp>When using Native API to set the registry, the structure OBJECT_ATTRIBUTES is required as a parameter to specify the length of the string to be read.\u003C\u002Fp>\u003Cp>As long as the length is set correctly, the correct string can be read, avoiding this bug.\u003C\u002Fp>\u003Cp>\u003Cstrong>The key to exploitation:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Using Native API provides an additional parameter that allows specifying the length of the string to be read.\u003C\u002Fp>\u003Cp>Further consideration of this issue leads to the following test:\u003C\u002Fp>\u003Ch2>0x03 What exactly is the error when reading with Win32 API?\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Using HiddenNtRegistry to create a test registry key-value, the C++ calling code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>printf(\"=================Normal Key=================\\n\");\u003Cbr>printf(\"1.CreateKey:\\n\");\u003Cbr>MyCreateKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test1\");\u003Cbr>printf(\"2.OpenKey:\\n\");\u003Cbr>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test1\");\u003Cbr>printf(\"3.SetValueKey:\\n\");\u003Cbr>MySetValueKey(hKey,\"test1\",\"0123456789abcdef\",REG_SZ);\u003Cbr>\u003Cbr>printf(\"=================Hidden Key=================\\n\");\u003Cbr>printf(\"1.OpenKey:\\n\");\u003Cbr>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test1\");\u003Cbr>printf(\"2.SetHiddenValueKey:\\n\");\u003Cbr>MySetHiddenValueKey(hKey,\"\\0test1\",\"hidden0123456789abcdef\",REG_SZ);\u003Cbr>printf(\"3.QueryHiddenValueKey:\\n\");\u003Cbr>MyQueryHiddenValueKeyString(hKey,\"\\0test1\");\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The program implements the following functions:\u003C\u002Fp>\u003Cul>\u003Cli>Create registry key value test1 with content 0123456789abcdef\u003C\u002Fli>\u003Cli>Create registry key value \\0test1 with content hidden0123456789abcdef\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Run as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770017968971_0_2774684c27.jpeg\">\u003C\u002Fp>\u003Cp>Use Win32 API RegQueryValueEx to attempt reading the above two registry key values\u003C\u002Fp>\u003Cp>The key code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>LONG lReturnCode = 0;\u003Cbr>HKEY hkey;\u003Cbr>LPCTSTR RegPath = _T(\"Software\\\\test1\");\u003Cbr>if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegPath, 0, KEY_READ, &amp;hkey))\u003Cbr>{\u003Cbr>\tchar dwValue[1024];\u003Cbr>\tDWORD dwSzType = REG_SZ;\u003Cbr>\tDWORD dwSize = sizeof(dwValue);\u003Cbr>\tlReturnCode = ::RegQueryValueEx(hkey, _T(\"test1\"), 0, &amp;dwSzType, (LPBYTE)&amp;dwValue, &amp;dwSize);\u003Cbr>\tif(lReturnCode != ERROR_SUCCESS)\u003Cbr>\t{\u003Cbr>\t\tprintf(\"lReturnCode:%d\\n\",lReturnCode);\u003Cbr>\t\tif(lReturnCode = 2)\u003Cbr>\t\t\tprintf(\"ERROR_FILE_NOT_FOUND\\n\");\u003Cbr>\t\t\treturn 0;\u003Cbr>\t\t}\u003Cbr>\t\tprintf(\"RegQueryValue:\");\u003Cbr>\t\tfor (int i=0;i\u003Cdwsize 2-1;i++)\u003Cbr=\"\">\t\t{\u003Cbr>\t\t\tprintf(\"%c\",dwValue[i*2]);\u003Cbr>\t\t}\u003Cbr>\t}\u003Cbr>\t::RegCloseKey(hkey);\u003C\u002Fdwsize>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Read registry key value test1, successfully obtained content\u003C\u002Fp>\u003Cp>Read registry key value \\0test1, modified code as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>lReturnCode = ::RegQueryValueEx(hkey, _T(\"\\0test1\"), 0, &amp;dwSzType, (LPBYTE)&amp;dwValue, &amp;dwSize);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Read failed, returned ERROR_FILE_NOT_FOUND\u003C\u002Fp>\u003Cp>Verify the principle above: Due to the effect of \"\\0\", the string is truncated early, recognized as a null character, resulting in inability to obtain the name\u003C\u002Fp>\u003Cp>Then proceed with further attempts\u003C\u002Fp>\u003Ch2>0x04 What happens when \"\\0\" is placed in the middle of a string?\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The code for HiddenNtRegistry is:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>printf(\"1.OpenKey:\\n\");\u003Cbr>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test2\");\u003Cbr>printf(\"2.SetHiddenValueKey:\\n\");\u003Cbr>MySetHiddenValueKey2(hKey,\"test2\\0abc\",\"hidden0123456789abcdef\",REG_SZ);\u003Cbr>printf(\"3.QueryHiddenValueKey:\\n\");\u003Cbr>MyQueryHiddenValueKeyString2(hKey,\"test2\\0abc\");\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>The MySetHiddenValueKey and MyQueryHiddenValueKeyString functions in the original HiddenNtRegistry project need to be appropriately modified to recalculate string lengths. The new functions are named MySetHiddenValueKey2 and MyQueryHiddenValueKeyString2.\u003C\u002Fp>\u003Cp>The program implements the following functions:\u003C\u002Fp>\u003Cul>\u003Cli>Create a registry key value test2\\0abc with content hidden0123456789abcdef\u003C\u002Fli>\u003Cli>Read the content of the registry key value test2\\0abc\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The runtime is as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770017975589_1_7e6c390f22.jpeg\">\u003C\u002Fp>\u003Cp>Using regedit.exe to query this key value, a pop-up prompts that it cannot be obtained, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770017986075_2_55c8a6b40c.jpeg\">\u003C\u002Fp>\u003Cp>Here we can make a bold attempt:\u003C\u002Fp>\u003Cp>\u003Cstrong>Since the \"\\0\" in test2\\0abc truncates the string, what happens if we create another key value named test2?\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Create the registry key value test2 with content 0123456789abcdef, the key code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test2\");\u003Cbr>MySetValueKey(hKey,\"test2\",\"0123456789abcdef\",REG_SZ);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Using regedit.exe to view the registry again, something interesting happens, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770017990622_3_8657edab2d.jpeg\">\u003C\u002Fp>\u003Cp>Querying the registry key value \\\\Registry\\\\Machine\\\\Software\\\\test2 no longer pops up an error, but displays two key values named test2, both with content 0123456789abcdef\u003C\u002Fp>\u003Cp>We know that the registry does not allow creating two key values with the same name. The two key values with the same name generated in the above test are actually because one of them is incorrectly truncated, resulting in the same displayed key name and content, both being 0123456789abcdef (actually the content is hidden0123456789abcdef)\u003C\u002Fp>\u003Cp>Thus, we have another method to \"hide\" registry entries. Compared to the previous method of filling \"\\0\" at the beginning, the biggest advantage of this hiding method is that using regedit.exe to view this key value does not pop up an error, providing better concealment and deception, while having the same content as a normal key value\u003C\u002Fp>\u003Cp>Comparison as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770017995203_4_88a788c1f1.jpeg\">\u003C\u002Fp>\u003Cp>The displayed key value content is 0123456789abcdef, but the actual content is hidden0123456789abcdef\u003C\u002Fp>\u003Ch2>0x05 Can other Native APIs (such as NtCreateFile) be applied?\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Referencing the implementation approach of NtCreateKey, test other Native APIs, such as NtCreateFile, to see if the same issue exists when creating files?\u003C\u002Fp>\u003Cp>Using NtCreateFile to create a special file: \\0c:\\1\\test.txt, key code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>HMODULE             hModule\t\t\t\t= NULL;\u003Cbr>NTCREATEFILE        NtCreateFile\t\t= NULL;\u003Cbr>UNICODE_STRING      FileName\t\t\t= {0};\u003Cbr>OBJECT_ATTRIBUTES   ObjectAttributes\t= {0};\u003Cbr>HANDLE              hFile1\t\t\t\t= NULL;\u003Cbr>IO_STATUS_BLOCK     IOsb\t\t\t\t= {0};\u003Cbr>HANDLE              hFile2\t\t\t\t= INVALID_HANDLE_VALUE;\u003Cbr>PWCHAR              pBuffer\t\t\t\t= NULL;\u003Cbr>DWORD               dwRet\t\t\t\t= 0;\u003Cbr>hModule = LoadLibrary(_T(\"ntdll.dll\"));\u003Cbr>if (!hModule)     \u003Cbr>{  \u003Cbr>\tprintf(\"Could not GetModuleHandle of NTDLL.DLL\");\u003Cbr>\treturn FALSE;\u003Cbr>}   \u003Cbr>NtCreateFile = (NTCREATEFILE)GetProcAddress(hModule, \"NtCreateFile\");  \u003Cbr>if (!NtCreateFile) \u003Cbr>{\u003Cbr>\tprintf(\"Could not find NtCreateFile entry point in NTDLL.DLL\");\u003Cbr>\treturn FALSE;\u003Cbr>}\t\u003Cbr>char *Path = \"\\\\Device\\\\\\HarddiskVolume1\\\\1\\\\test.txt\";\u003Cbr>char *TempBuff;\u003Cbr>TempBuff = (char*)malloc(strlen(Path+2)*2);\u003Cbr>for(int i=0;i\u003Cstrlen(path);i++)\u003Cbr>{\u003Cbr>\tTempBuff[(i+2)*2] = Path[i];\u003Cbr>\tTempBuff[(i+2)*2+1] = 0x00;\u003Cbr>}\u003Cbr>TempBuff[0] = 0x00;\u003Cbr>TempBuff[1] = 0x00;\u003Cbr>TempBuff[2] = 0x00;\u003Cbr>TempBuff[3] = 0x00;\u003Cbr>FileName.MaximumLength = MAX_PATH * sizeof(WCHAR);\u003Cbr>FileName.Length = (strlen(Path)+2)*sizeof(WCHAR);\u003Cbr>FileName.Buffer = (WCHAR *)TempBuff;\u003Cbr>FileName.Buffer[FileName.Length] = L'\\0';\u003Cbr>InitializeObjectAttributes(&amp;ObjectAttributes,&amp;FileName,OBJ_CASE_INSENSITIVE,NULL,NULL);\u003Cbr>NtStatus = NtCreateFile(&amp;hFile1,\u003Cbr>\t\t\t\t\t\t\tFILE_GENERIC_WRITE,\u003Cbr>\t\t\t\t\t\t\t&amp;ObjectAttributes,\u003Cbr>\t\t\t\t\t\t\t&amp;IOsb,  \u003Cbr>\t\t\t\t\t\t\tNULL,  \u003Cbr>\t\t\t\t\t\t\tFILE_ATTRIBUTE_NORMAL,  \u003Cbr>\t\t\t\t\t\t\t0,\u003Cbr>\t\t\t\t\t\t\tFILE_SUPERSEDE,  \u003Cbr>\t\t\t\t\t\t\tFILE_SEQUENTIAL_ONLY,\u003Cbr>\t\t\t\t\t\t\tNULL,  \u003Cbr>\t\t\t\t\t\t\t0  \u003Cbr>\t\t\t\t\t\t\t); \u003Cbr>if (!NT_SUCCESS(NtStatus))  \u003Cbr>{  \u003Cbr>\tprintf(\"NtCreateFile failed (%x) \\n\", NtStatus);            \u003Cbr>}  \u003Cbr>else  \u003Cbr>\tprintf(\"NtCreateFile succeed \\n\"); \u003C\u002Fstrlen(path);i++)\u003Cbr>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return error c000003b, indicating STATUS_OBJECT_PATH_SYNTAX_BAD\u003C\u002Fp>\u003Cp>Debug the program, trace to InitializeObjectAttributes, and examine the parameters of the ObjectAttributes structure, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770017998608_5_baf7ac3af8.jpeg\">\u003C\u002Fp>\u003Cp>Examine the content of Buffer in memory, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770018001912_6_225f1fa245.jpeg\">\u003C\u002Fp>\u003Cp>Same parameter structure as used in NtCreateKey implementation\u003C\u002Fp>\u003Cp>For NtCreateFile, currently not applicable\u003C\u002Fp>\u003Ch2>0x06 Exploitation Ideas and Detection\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The registry hiding technique used by Poweliks has a major issue: it triggers an error pop-up when opened with regedit.exe. Inserting \\0 in the middle of a string and creating a key with the same name as the substring before \\0 can avoid this problem\u003C\u002Fp>\u003Cp>The detection approach is to identify such unusual registry keys and check if two keys with the same name exist under a registry key\u003C\u002Fp>\u003Cp>If this method is used to create registry keys in startup locations, Autoruns can detect them\u003C\u002Fp>\u003Ch2>0x07 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article further tests the registry hiding techniques used by Poweliks, shares a more covert exploitation method, and provides ideas for defense and detection. More testing is needed for the application of other Native APIs.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","text","ltr","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The previous article 'Penetration Techniques - Creation of \"Hidden\" Registry' introduced the registry hiding technique used by Poweliks, analyzed its principles, and implemented the functionality through a C program.\u003C\u002Fp>\u003Cp>This article will conduct further testing and share a more \"stealthy\" method (this method has not been found in public materials yet, pending confirmation).\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Errors when reading with Win32 API\u003C\u002Fli>\u003Cli>The case of placing \"\\0\" in the middle of a string\u003C\u002Fli>\u003Cli>Application of other Native APIs (such as NtCreateFile)\u003C\u002Fli>\u003Cli>More covert exploitation methods\u003C\u002Fli>\u003Cli>Defense and detection\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Hiding Principle\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>For Windows systems, \"\\0\" (i.e., 0x0000) is recognized as the string terminator.\u003C\u002Fp>\u003Cp>Therefore, during the reading of such a string, encountering a leading \"\\0\" will be interpreted as the terminator, causing premature truncation and leading to a read error.\u003C\u002Fp>\u003Cp>When using Native API to set the registry, the structure OBJECT_ATTRIBUTES is required as a parameter to specify the length of the string to be read.\u003C\u002Fp>\u003Cp>As long as the length is set correctly, the correct string can be read, avoiding this bug.\u003C\u002Fp>\u003Cp>\u003Cstrong>The key to exploitation:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Using Native API provides an additional parameter that allows specifying the length of the string to be read.\u003C\u002Fp>\u003Cp>Further consideration of this issue leads to the following test:\u003C\u002Fp>\u003Ch2>0x03 What exactly is the error when reading with Win32 API?\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Using HiddenNtRegistry to create a test registry key-value, the C++ calling code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>printf(\"=================Normal Key=================\\n\");\u003Cbr>printf(\"1.CreateKey:\\n\");\u003Cbr>MyCreateKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test1\");\u003Cbr>printf(\"2.OpenKey:\\n\");\u003Cbr>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test1\");\u003Cbr>printf(\"3.SetValueKey:\\n\");\u003Cbr>MySetValueKey(hKey,\"test1\",\"0123456789abcdef\",REG_SZ);\u003Cbr>\u003Cbr>printf(\"=================Hidden Key=================\\n\");\u003Cbr>printf(\"1.OpenKey:\\n\");\u003Cbr>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test1\");\u003Cbr>printf(\"2.SetHiddenValueKey:\\n\");\u003Cbr>MySetHiddenValueKey(hKey,\"\\0test1\",\"hidden0123456789abcdef\",REG_SZ);\u003Cbr>printf(\"3.QueryHiddenValueKey:\\n\");\u003Cbr>MyQueryHiddenValueKeyString(hKey,\"\\0test1\");\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The program implements the following functions:\u003C\u002Fp>\u003Cul>\u003Cli>Create registry key value test1 with content 0123456789abcdef\u003C\u002Fli>\u003Cli>Create registry key value \\0test1 with content hidden0123456789abcdef\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Run as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770017968971_0_2774684c27-1.jpeg\">\u003C\u002Fp>\u003Cp>Use Win32 API RegQueryValueEx to attempt reading the above two registry key values\u003C\u002Fp>\u003Cp>The key code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>LONG lReturnCode = 0;\u003Cbr>HKEY hkey;\u003Cbr>LPCTSTR RegPath = _T(\"Software\\\\test1\");\u003Cbr>if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegPath, 0, KEY_READ, &amp;hkey))\u003Cbr>{\u003Cbr>\tchar dwValue[1024];\u003Cbr>\tDWORD dwSzType = REG_SZ;\u003Cbr>\tDWORD dwSize = sizeof(dwValue);\u003Cbr>\tlReturnCode = ::RegQueryValueEx(hkey, _T(\"test1\"), 0, &amp;dwSzType, (LPBYTE)&amp;dwValue, &amp;dwSize);\u003Cbr>\tif(lReturnCode != ERROR_SUCCESS)\u003Cbr>\t{\u003Cbr>\t\tprintf(\"lReturnCode:%d\\n\",lReturnCode);\u003Cbr>\t\tif(lReturnCode = 2)\u003Cbr>\t\t\tprintf(\"ERROR_FILE_NOT_FOUND\\n\");\u003Cbr>\t\t\treturn 0;\u003Cbr>\t\t}\u003Cbr>\t\tprintf(\"RegQueryValue:\");\u003Cbr>\t\tfor (int i=0;i\u003Cdwsize 2-1;i++)\u003Cbr=\"\">\t\t{\u003Cbr>\t\t\tprintf(\"%c\",dwValue[i*2]);\u003Cbr>\t\t}\u003Cbr>\t}\u003Cbr>\t::RegCloseKey(hkey);\u003C\u002Fdwsize>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Read registry key value test1, successfully obtained content\u003C\u002Fp>\u003Cp>Read registry key value \\0test1, modified code as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>lReturnCode = ::RegQueryValueEx(hkey, _T(\"\\0test1\"), 0, &amp;dwSzType, (LPBYTE)&amp;dwValue, &amp;dwSize);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Read failed, returned ERROR_FILE_NOT_FOUND\u003C\u002Fp>\u003Cp>Verify the principle above: Due to the effect of \"\\0\", the string is truncated early, recognized as a null character, resulting in inability to obtain the name\u003C\u002Fp>\u003Cp>Then proceed with further attempts\u003C\u002Fp>\u003Ch2>0x04 What happens when \"\\0\" is placed in the middle of a string?\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The code for HiddenNtRegistry is:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>printf(\"1.OpenKey:\\n\");\u003Cbr>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test2\");\u003Cbr>printf(\"2.SetHiddenValueKey:\\n\");\u003Cbr>MySetHiddenValueKey2(hKey,\"test2\\0abc\",\"hidden0123456789abcdef\",REG_SZ);\u003Cbr>printf(\"3.QueryHiddenValueKey:\\n\");\u003Cbr>MyQueryHiddenValueKeyString2(hKey,\"test2\\0abc\");\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>The MySetHiddenValueKey and MyQueryHiddenValueKeyString functions in the original HiddenNtRegistry project need to be appropriately modified to recalculate string lengths. The new functions are named MySetHiddenValueKey2 and MyQueryHiddenValueKeyString2.\u003C\u002Fp>\u003Cp>The program implements the following functions:\u003C\u002Fp>\u003Cul>\u003Cli>Create a registry key value test2\\0abc with content hidden0123456789abcdef\u003C\u002Fli>\u003Cli>Read the content of the registry key value test2\\0abc\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The runtime is as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770017975589_1_7e6c390f22-1.jpeg\">\u003C\u002Fp>\u003Cp>Using regedit.exe to query this key value, a pop-up prompts that it cannot be obtained, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770017986075_2_55c8a6b40c-1.jpeg\">\u003C\u002Fp>\u003Cp>Here we can make a bold attempt:\u003C\u002Fp>\u003Cp>\u003Cstrong>Since the \"\\0\" in test2\\0abc truncates the string, what happens if we create another key value named test2?\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Create the registry key value test2 with content 0123456789abcdef, the key code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>hKey = MyOpenKey(\"\\\\Registry\\\\Machine\\\\Software\\\\test2\");\u003Cbr>MySetValueKey(hKey,\"test2\",\"0123456789abcdef\",REG_SZ);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Using regedit.exe to view the registry again, something interesting happens, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770017990622_3_8657edab2d-1.jpeg\">\u003C\u002Fp>\u003Cp>Querying the registry key value \\\\Registry\\\\Machine\\\\Software\\\\test2 no longer pops up an error, but displays two key values named test2, both with content 0123456789abcdef\u003C\u002Fp>\u003Cp>We know that the registry does not allow creating two key values with the same name. The two key values with the same name generated in the above test are actually because one of them is incorrectly truncated, resulting in the same displayed key name and content, both being 0123456789abcdef (actually the content is hidden0123456789abcdef)\u003C\u002Fp>\u003Cp>Thus, we have another method to \"hide\" registry entries. Compared to the previous method of filling \"\\0\" at the beginning, the biggest advantage of this hiding method is that using regedit.exe to view this key value does not pop up an error, providing better concealment and deception, while having the same content as a normal key value\u003C\u002Fp>\u003Cp>Comparison as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770017995203_4_88a788c1f1-1.jpeg\">\u003C\u002Fp>\u003Cp>The displayed key value content is 0123456789abcdef, but the actual content is hidden0123456789abcdef\u003C\u002Fp>\u003Ch2>0x05 Can other Native APIs (such as NtCreateFile) be applied?\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Referencing the implementation approach of NtCreateKey, test other Native APIs, such as NtCreateFile, to see if the same issue exists when creating files?\u003C\u002Fp>\u003Cp>Using NtCreateFile to create a special file: \\0c:\\1\\test.txt, key code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>HMODULE             hModule\t\t\t\t= NULL;\u003Cbr>NTCREATEFILE        NtCreateFile\t\t= NULL;\u003Cbr>UNICODE_STRING      FileName\t\t\t= {0};\u003Cbr>OBJECT_ATTRIBUTES   ObjectAttributes\t= {0};\u003Cbr>HANDLE              hFile1\t\t\t\t= NULL;\u003Cbr>IO_STATUS_BLOCK     IOsb\t\t\t\t= {0};\u003Cbr>HANDLE              hFile2\t\t\t\t= INVALID_HANDLE_VALUE;\u003Cbr>PWCHAR              pBuffer\t\t\t\t= NULL;\u003Cbr>DWORD               dwRet\t\t\t\t= 0;\u003Cbr>hModule = LoadLibrary(_T(\"ntdll.dll\"));\u003Cbr>if (!hModule)     \u003Cbr>{  \u003Cbr>\tprintf(\"Could not GetModuleHandle of NTDLL.DLL\");\u003Cbr>\treturn FALSE;\u003Cbr>}   \u003Cbr>NtCreateFile = (NTCREATEFILE)GetProcAddress(hModule, \"NtCreateFile\");  \u003Cbr>if (!NtCreateFile) \u003Cbr>{\u003Cbr>\tprintf(\"Could not find NtCreateFile entry point in NTDLL.DLL\");\u003Cbr>\treturn FALSE;\u003Cbr>}\t\u003Cbr>char *Path = \"\\\\Device\\\\\\HarddiskVolume1\\\\1\\\\test.txt\";\u003Cbr>char *TempBuff;\u003Cbr>TempBuff = (char*)malloc(strlen(Path+2)*2);\u003Cbr>for(int i=0;i\u003Cstrlen(path);i++)\u003Cbr>{\u003Cbr>\tTempBuff[(i+2)*2] = Path[i];\u003Cbr>\tTempBuff[(i+2)*2+1] = 0x00;\u003Cbr>}\u003Cbr>TempBuff[0] = 0x00;\u003Cbr>TempBuff[1] = 0x00;\u003Cbr>TempBuff[2] = 0x00;\u003Cbr>TempBuff[3] = 0x00;\u003Cbr>FileName.MaximumLength = MAX_PATH * sizeof(WCHAR);\u003Cbr>FileName.Length = (strlen(Path)+2)*sizeof(WCHAR);\u003Cbr>FileName.Buffer = (WCHAR *)TempBuff;\u003Cbr>FileName.Buffer[FileName.Length] = L'\\0';\u003Cbr>InitializeObjectAttributes(&amp;ObjectAttributes,&amp;FileName,OBJ_CASE_INSENSITIVE,NULL,NULL);\u003Cbr>NtStatus = NtCreateFile(&amp;hFile1,\u003Cbr>\t\t\t\t\t\t\tFILE_GENERIC_WRITE,\u003Cbr>\t\t\t\t\t\t\t&amp;ObjectAttributes,\u003Cbr>\t\t\t\t\t\t\t&amp;IOsb,  \u003Cbr>\t\t\t\t\t\t\tNULL,  \u003Cbr>\t\t\t\t\t\t\tFILE_ATTRIBUTE_NORMAL,  \u003Cbr>\t\t\t\t\t\t\t0,\u003Cbr>\t\t\t\t\t\t\tFILE_SUPERSEDE,  \u003Cbr>\t\t\t\t\t\t\tFILE_SEQUENTIAL_ONLY,\u003Cbr>\t\t\t\t\t\t\tNULL,  \u003Cbr>\t\t\t\t\t\t\t0  \u003Cbr>\t\t\t\t\t\t\t); \u003Cbr>if (!NT_SUCCESS(NtStatus))  \u003Cbr>{  \u003Cbr>\tprintf(\"NtCreateFile failed (%x) \\n\", NtStatus);            \u003Cbr>}  \u003Cbr>else  \u003Cbr>\tprintf(\"NtCreateFile succeed \\n\"); \u003C\u002Fstrlen(path);i++)\u003Cbr>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return error c000003b, indicating STATUS_OBJECT_PATH_SYNTAX_BAD\u003C\u002Fp>\u003Cp>Debug the program, trace to InitializeObjectAttributes, and examine the parameters of the ObjectAttributes structure, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770017998608_5_baf7ac3af8-1.jpeg\">\u003C\u002Fp>\u003Cp>Examine the content of Buffer in memory, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770018001912_6_225f1fa245-1.jpeg\">\u003C\u002Fp>\u003Cp>Same parameter structure as used in NtCreateKey implementation\u003C\u002Fp>\u003Cp>For NtCreateFile, currently not applicable\u003C\u002Fp>\u003Ch2>0x06 Exploitation Ideas and Detection\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The registry hiding technique used by Poweliks has a major issue: it triggers an error pop-up when opened with regedit.exe. Inserting \\0 in the middle of a string and creating a key with the same name as the substring before \\0 can avoid this problem\u003C\u002Fp>\u003Cp>The detection approach is to identify such unusual registry keys and check if two keys with the same name exist under a registry key\u003C\u002Fp>\u003Cp>If this method is used to create registry keys in startup locations, Autoruns can detect them\u003C\u002Fp>\u003Ch2>0x07 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article further tests the registry hiding techniques used by Poweliks, shares a more covert exploitation method, and provides ideas for defense and detection. More testing is needed for the application of other Native APIs.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",988,"Onedaysec",5,"published","2026-02-02T07:51:00.061Z",{"title":37,"description":14,"keywords":38,"ogImage":39,"canonicalUrl":39,"noIndex":40},"Hidden Registry Penetration Testing: Stealthy Exploits & Defense","registry hiding, penetration testing, Native API, stealth techniques, Windows security, Poweliks, hidden registry keys, zero-day exploits, cybersecurity, defense detection",null,false,[],{"docs":43,"hasNextPage":40},[44,45,46,47,4],577,576,575,574,{"title":39,"description":39,"image":39},"2026-07-24T15:37:12.640Z","2026-07-23T16:01:45.705Z","draft","2026-07-23T16:13:27.292Z"]