0x00 Preface

---

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.

This article will conduct further testing and share a more "stealthy" method (this method has not been found in public materials yet, pending confirmation).

0x01 Introduction

---

This article will cover the following topics:

  • Errors when reading with Win32 API
  • The case of placing "\0" in the middle of a string
  • Application of other Native APIs (such as NtCreateFile)
  • More covert exploitation methods
  • Defense and detection

0x02 Hiding Principle

---

For Windows systems, "\0" (i.e., 0x0000) is recognized as the string terminator.

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.

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.

As long as the length is set correctly, the correct string can be read, avoiding this bug.

The key to exploitation:

Using Native API provides an additional parameter that allows specifying the length of the string to be read.

Further consideration of this issue leads to the following test:

0x03 What exactly is the error when reading with Win32 API?

---

Using HiddenNtRegistry to create a test registry key-value, the C++ calling code is as follows:

printf("=================Normal Key=================\n");
printf("1.CreateKey:\n");
MyCreateKey("\\Registry\\Machine\\Software\\test1");
printf("2.OpenKey:\n");
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test1");
printf("3.SetValueKey:\n");
MySetValueKey(hKey,"test1","0123456789abcdef",REG_SZ);

printf("=================Hidden Key=================\n");
printf("1.OpenKey:\n");
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test1");
printf("2.SetHiddenValueKey:\n");
MySetHiddenValueKey(hKey,"\0test1","hidden0123456789abcdef",REG_SZ);
printf("3.QueryHiddenValueKey:\n");
MyQueryHiddenValueKeyString(hKey,"\0test1");

The program implements the following functions:

  • Create registry key value test1 with content 0123456789abcdef
  • Create registry key value \0test1 with content hidden0123456789abcdef

Run as shown in the figure below

Alt text

Use Win32 API RegQueryValueEx to attempt reading the above two registry key values

The key code is as follows:

LONG lReturnCode = 0;
HKEY hkey;
LPCTSTR RegPath = _T("Software\\test1");
if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegPath, 0, KEY_READ, &hkey))
{
char dwValue[1024];
DWORD dwSzType = REG_SZ;
DWORD dwSize = sizeof(dwValue);
lReturnCode = ::RegQueryValueEx(hkey, _T("test1"), 0, &dwSzType, (LPBYTE)&dwValue, &dwSize);
if(lReturnCode != ERROR_SUCCESS)
{
printf("lReturnCode:%d\n",lReturnCode);
if(lReturnCode = 2)
printf("ERROR_FILE_NOT_FOUND\n");
return 0;
}
printf("RegQueryValue:");
for (int i=0;i {
printf("%c",dwValue[i*2]);
}
}
::RegCloseKey(hkey);

Read registry key value test1, successfully obtained content

Read registry key value \0test1, modified code as follows:

lReturnCode = ::RegQueryValueEx(hkey, _T("\0test1"), 0, &dwSzType, (LPBYTE)&dwValue, &dwSize);

Read failed, returned ERROR_FILE_NOT_FOUND

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

Then proceed with further attempts

0x04 What happens when "\0" is placed in the middle of a string?

---

The code for HiddenNtRegistry is:

printf("1.OpenKey:\n");
hKey = MyOpenKey("\\Registry\\Machine\\Software\\test2");
printf("2.SetHiddenValueKey:\n");
MySetHiddenValueKey2(hKey,"test2\0abc","hidden0123456789abcdef",REG_SZ);
printf("3.QueryHiddenValueKey:\n");
MyQueryHiddenValueKeyString2(hKey,"test2\0abc");

Note:

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.

The program implements the following functions:

  • Create a registry key value test2\0abc with content hidden0123456789abcdef
  • Read the content of the registry key value test2\0abc

The runtime is as shown in the figure below

Alt text

Using regedit.exe to query this key value, a pop-up prompts that it cannot be obtained, as shown in the figure below

Alt text

Here we can make a bold attempt:

Since the "\0" in test2\0abc truncates the string, what happens if we create another key value named test2?

Create the registry key value test2 with content 0123456789abcdef, the key code is as follows:

hKey = MyOpenKey("\\Registry\\Machine\\Software\\test2");
MySetValueKey(hKey,"test2","0123456789abcdef",REG_SZ);

Using regedit.exe to view the registry again, something interesting happens, as shown in the figure below

Alt text

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

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)

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

Comparison as shown in the figure below

Alt text

The displayed key value content is 0123456789abcdef, but the actual content is hidden0123456789abcdef

0x05 Can other Native APIs (such as NtCreateFile) be applied?

---

Referencing the implementation approach of NtCreateKey, test other Native APIs, such as NtCreateFile, to see if the same issue exists when creating files?

Using NtCreateFile to create a special file: \0c:\1\test.txt, key code is as follows:

HMODULE hModule = NULL;
NTCREATEFILE NtCreateFile = NULL;
UNICODE_STRING FileName = {0};
OBJECT_ATTRIBUTES ObjectAttributes = {0};
HANDLE hFile1 = NULL;
IO_STATUS_BLOCK IOsb = {0};
HANDLE hFile2 = INVALID_HANDLE_VALUE;
PWCHAR pBuffer = NULL;
DWORD dwRet = 0;
hModule = LoadLibrary(_T("ntdll.dll"));
if (!hModule)
{
printf("Could not GetModuleHandle of NTDLL.DLL");
return FALSE;
}
NtCreateFile = (NTCREATEFILE)GetProcAddress(hModule, "NtCreateFile");
if (!NtCreateFile)
{
printf("Could not find NtCreateFile entry point in NTDLL.DLL");
return FALSE;
}
char *Path = "\\Device\\\HarddiskVolume1\\1\\test.txt";
char *TempBuff;
TempBuff = (char*)malloc(strlen(Path+2)*2);
for(int i=0;i{
TempBuff[(i+2)*2] = Path[i];
TempBuff[(i+2)*2+1] = 0x00;
}
TempBuff[0] = 0x00;
TempBuff[1] = 0x00;
TempBuff[2] = 0x00;
TempBuff[3] = 0x00;
FileName.MaximumLength = MAX_PATH * sizeof(WCHAR);
FileName.Length = (strlen(Path)+2)*sizeof(WCHAR);
FileName.Buffer = (WCHAR *)TempBuff;
FileName.Buffer[FileName.Length] = L'\0';
InitializeObjectAttributes(&ObjectAttributes,&FileName,OBJ_CASE_INSENSITIVE,NULL,NULL);
NtStatus = NtCreateFile(&hFile1,
FILE_GENERIC_WRITE,
&ObjectAttributes,
&IOsb,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_SUPERSEDE,
FILE_SEQUENTIAL_ONLY,
NULL,
0
);
if (!NT_SUCCESS(NtStatus))
{
printf("NtCreateFile failed (%x) \n", NtStatus);
}
else
printf("NtCreateFile succeed \n");

Return error c000003b, indicating STATUS_OBJECT_PATH_SYNTAX_BAD

Debug the program, trace to InitializeObjectAttributes, and examine the parameters of the ObjectAttributes structure, as shown in the figure below

Alt text

Examine the content of Buffer in memory, as shown in the figure below

Alt text

Same parameter structure as used in NtCreateKey implementation

For NtCreateFile, currently not applicable

0x06 Exploitation Ideas and Detection

---

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

The detection approach is to identify such unusual registry keys and check if two keys with the same name exist under a registry key

If this method is used to create registry keys in startup locations, Autoruns can detect them

0x07 Summary

---

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.