In kernel programming, strings have two formats: ANSI_STRING
and UNICODE_STRING
. These two formats are safe versions of string structures introduced by Microsoft and are also formats recommended by Microsoft. , usually the type represented by ANSI_STRING
is char *
, which is a string in multi-byte mode of ANSI
, and UNICODE_STRING
represents wchar*
, which is a character of type UNCODE
. The following article will introduce how these two character formats are converted in the kernel.
In the Windows kernel, string processing is very important. Unlike user-mode programs, strings in the kernel must follow strict security rules to ensure that various security vulnerabilities are not caused.
ANSI_STRING
and UNICODE_STRING
are two secure versions of string structures introduced by Microsoft in the kernel. ANSI_STRING
represents ANSI
Multi-byte mode string, and UNICODE_STRING
represents the UNCODE
type character. These two string types can be converted to each other, so in kernel programming, type conversion is required frequently.
The conversion between ANSI_STRING
and UNICODE_STRING
can be achieved through a series of functions provided in the kernel. Among them, the two most commonly used functions are RtlUnicodeStringToAnsiString
and RtlAnsiStringToUnicodeString
. These two functions are used to convert a string of type UNICODE_STRING
into a string of type ANSI_STRING
, and to convert a string of type ANSI_STRING
into a string of type UNICODE_STRING
.
2.3.1 Initialization string
In the kernel development mode, initializing string
also needs to call a dedicated initialization function. When using ANSI
string, you need to call the RtlInitAnsiString
function for initialization. When using Unicode
string, you need to call the RtlInitUnicodeString
function for initialization. Both functions need to pass in the string to be initialized and the length of the string. After the initialization is completed, the string can be used. The ANSI
and UNCODE
strings are initialized as follows respectively. Let’s take a look at how the code is implemented.
#include <ntifs.h> #include <ntstrsafe.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstallation successful\\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> //Define kernel string ANSI_STRING ansi; UNICODE_STRING unicode; UNICODE_STRING str; //Define ordinary string char * char_string = "hello lyshark"; wchar_t *wchar_string = (WCHAR*)"hello lyshark"; // Various ways to initialize strings RtlInitAnsiString( & amp;ansi, char_string); RtlInitUnicodeString( & amp;unicode, wchar_string); RtlUnicodeStringInit( & amp;str, L"hello lyshark"); //Change the original string (garbled position, here is only used to demonstrate the assignment method) char_string[0] = (CHAR)"A"; // Each char type occupies 1 byte char_string[1] = (CHAR)"B"; wchar_string[0] = (WCHAR)"A"; // Each wchar type occupies 2 bytes wchar_string[2] = (WCHAR)"B"; // Output string %Z DbgPrint("Output ANSI: %Z \\ ", & amp;ansi); DbgPrint("Output WCHAR: %Z \\ ", & amp;unicode); DbgPrint("Output string: %wZ \\ ", & amp;str); DbgPrint("Driver loaded successfully \\ "); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is shown in the figure below;
2.3.2 String and integer conversion
The kernel can also realize flexible conversion between string and integer
. The kernel provides the function RtlUnicodeStringToInteger
to realize string to integer
. The corresponding RtlIntegerToUnicodeString
converts integer to string
. These two kernel functions are also very commonly used.
The RtlUnicodeStringToInteger
function is usually used to convert a Unicode
string into an integer. The function prototype is:
NTSYSAPI NTSTATUS NTAPI RtlUnicodeStringToInteger( PCUNICODE_STRING String, ULONG Base, PULONG Value );
Among them, the String
parameter is the input Unicode
string, the Base
parameter is a base number (usually decimal), and the Value
The parameter is the integer output. The return value is the function execution status. If successful, STATUS_SUCCESS
is returned.
Corresponding to it is the RtlIntegerToUnicodeString
function, which is used to convert integers into Unicode
strings. The function prototype is:
NTSYSAPI NTSTATUS NTAPI RtlIntegerToUnicodeString( ULONG Value, ULONG Base, PUNICODE_STRING String );
Among them, the Value
parameter is the input integer, the Base
parameter is the base number, and the String
parameter is the output Unicode
string. The return value is also the function execution status. If successful, STATUS_SUCCESS
is returned.
#include <ntifs.h> #include <ntstrsafe.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstallation successful\\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> NTSTATUS flag; ULONG number; DbgPrint("hello lyshark \\ "); UNICODE_STRING uncode_buffer_source = {<!-- --> 0 }; UNICODE_STRING uncode_buffer_target = {<!-- --> 0 }; //Convert string to number RtlInitUnicodeString( & amp;uncode_buffer_source, L"100"); flag = RtlUnicodeStringToInteger( & amp;uncode_buffer_source, 10, & amp;number); if (NT_SUCCESS(flag)) {<!-- --> DbgPrint("String -> Number: %d \\ ", number); } //Convert number to string uncode_buffer_target.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024); uncode_buffer_target.MaximumLength = 1024; flag = RtlIntegerToUnicodeString(number, 10, & amp;uncode_buffer_target); if (NT_SUCCESS(flag)) {<!-- --> DbgPrint("Number -> String: %wZ \\ ", & amp;uncode_buffer_target); } // Release heap space RtlFreeUnicodeString( & amp;uncode_buffer_target); DbgPrint("Driver loaded successfully \\ "); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is shown in the figure below;
2.3.3 String ANSI and UNICODE
Convert the UNICODE_STRING
structure into the ANSI_STRING
structure. The code calls the RtlUnicodeStringToAnsiString
kernel function, which is also provided by Microsoft.
The core part of the code to convert the UNICODE_STRING
structure into the ANSI_STRING
structure can be summarized as:
ANSI_STRING AnsiStr; UNICODE_STRING UniStr; RtlUnicodeStringToAnsiString( & amp;AnsiStr, & amp;UniStr, TRUE);
Among them, AnsiStr
is the structure to store the converted ANSI
string, and UniStr
is the UNICODE
to be converted. String structure, the third parameter TRUE
indicates that a buffer should be allocated to store the converted string.
Note that when using the RtlUnicodeStringToAnsiString
function, you need to call the RtlFreeAnsiString
function to release the allocated buffer after use.
#include <ntifs.h> #include <ntstrsafe.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstallation successful\\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \\ "); UNICODE_STRING uncode_buffer_source = {<!-- --> 0 }; ANSI_STRING ansi_buffer_target = {<!-- --> 0 }; //Initialize UNICODE string RtlInitUnicodeString( & amp;uncode_buffer_source, L"hello lyshark"); // Conversion function NTSTATUS flag = RtlUnicodeStringToAnsiString( & amp;ansi_buffer_target, & amp;uncode_buffer_source, TRUE); if (NT_SUCCESS(flag)) {<!-- --> DbgPrint("ANSI: %Z \\ ", & amp;ansi_buffer_target); } // Destroy ANSI string RtlFreeAnsiString( & amp;ansi_buffer_target); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is shown in the figure below;
If you reverse the above process and convert ANSI_STRING
into a UNICODE_STRING
structure, you need to call the kernel-specific function RtlAnsiStringToUnicodeString
to implement it.
The function of RtlAnsiStringToUnicodeString
is to convert the ANSI_STRING
structure into the UNICODE_STRING
structure, where ANSI_STRING
represents ANSI
format string, and UNICODE_STRING
represents a Unicode
format string. The specific implementation process is as follows:
First, you need to define a ANSI_STRING
structure variable ansiStr
and initialize the Buffer, MaximumLength
and Length
member variables. The Buffer
member variable points to the buffer that stores the ANSI
format string, the MaximumLength
member variable indicates the maximum length of the buffer, and Length
Member variable indicates the length used in the buffer.
Then you need to define a UNICODE_STRING
structure variable uniStr
and initialize the Buffer, MaximumLength
and Length
member variables. The Buffer
member variable points to the buffer that stores the Unicode
format string, the MaximumLength
member variable indicates the maximum length of the buffer, and Length
Member variable indicates the length used in the buffer.
Call the RtlAnsiStringToUnicodeString
function and pass in two parameters. The first parameter is the UNICODE_STRING
structure pointer to be converted, and the second parameter is the ANSI_STRING< to be converted. /code>Structure pointer. The function will convert the content in
ANSI_STRING
to Unicode
format, and store the result in the Buffer
of the UNICODE_STRING
structure in member variables.
After the call is completed, the converted Unicode
format string is stored in uniStr.Buffer
and can be used for subsequent operations.
It should be noted that after using the RtlAnsiStringToUnicodeString
function, you need to call the RtlFreeUnicodeString
function to release the memory.
#include <ntifs.h> #include <ntstrsafe.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstallation successful\\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \\ "); UNICODE_STRING uncode_buffer_source = {<!-- --> 0 }; ANSI_STRING ansi_buffer_target = {<!-- --> 0 }; //Initialize string RtlInitString( & amp;ansi_buffer_target, "hello lyshark"); // Conversion function NTSTATUS flag = RtlAnsiStringToUnicodeString( & amp;uncode_buffer_source, & amp;ansi_buffer_target, TRUE); if (NT_SUCCESS(flag)) {<!-- --> DbgPrint("UNICODE: %wZ \\ ", & amp;uncode_buffer_source); } // Destroy UNICODE string RtlFreeUnicodeString( & amp;uncode_buffer_source); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is shown in the figure below;
The above code is a conversion type between common kernel structures. Sometimes we also need to convert various structures into ordinary character types, such as the two cases below:
For example, convert UNICODE_STRING
to CHAR*
type. Converting UNICODE_STRING
to CHAR*
type requires converting UNICODE_STRING
to ANSI_STRING
type first, and then converting ANSI_STRING
type is converted to CHAR*
type.
The specific steps can be summarized as follows:
- 1. Define variables of type
ANSI_STRING
andUNICODE_STRING
, which are used to store strings before and after conversion respectively; - 2. Call the
RtlUnicodeStringToAnsiString
function to convertUNICODE_STRING
to theANSI_STRING
type; - 3. Define a variable of type
CHAR*
to store the converted string; - 4. Convert the
ANSI_STRING
type to theCHAR*
type. You can use the character array pointed to byANSI_STRING.Buffer
asCHAR*
Type string.
The following is sample code that can be used to test the conversion modes of both;
#define _CRT_SECURE_NO_WARNINGS #include <ntifs.h> #include <windef.h> #include <ntstrsafe.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstallation successful\\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \\ "); UNICODE_STRING uncode_buffer_source = {<!-- --> 0 }; ANSI_STRING ansi_buffer_target = {<!-- --> 0 }; char szBuf[1024] = {<!-- --> 0 }; //Initialize UNICODE string RtlInitUnicodeString( & amp;uncode_buffer_source, L"hello lyshark"); // Conversion function NTSTATUS flag = RtlUnicodeStringToAnsiString( & amp;ansi_buffer_target, & amp;uncode_buffer_source, TRUE); if (NT_SUCCESS(flag)) {<!-- --> strcpy(szBuf, ansi_buffer_target.Buffer); DbgPrint("Output char* string: %s \\ ", szBuf); } // Destroy ANSI string RtlFreeAnsiString( & amp;ansi_buffer_target); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is as shown below:
If we implement the above process in reverse, there are two feasible ways to convert the CHAR*
type into a UNICODE_STRING
structure;
The first way can be achieved by calling the RtlCreateUnicodeStringFromAsciiz
function, which converts a CHAR*
type string into a UNICODE_STRING
structure. The function prototype is as follows:
NTSYSAPI BOOLEAN RtlCreateUnicodeStringFromAsciiz( PUNICODE_STRING DestinationString, PCSZ SourceString );
The function accepts two parameters, which are the target UNICODE_STRING
structure pointer and the source string pointer. Internally, the function will dynamically allocate memory and write the converted UNICODE_STRING
structure into the memory space pointed by the target structure pointer, and return a Boolean value to indicate whether the operation is successful. The specific usage of the function is as follows:
CHAR* srcString = "Hello, lyshark!"; UNICODE_STRING destString; RtlCreateUnicodeStringFromAsciiz( & amp;destString, srcString); //operate on destString RtlFreeUnicodeString( & amp;destString);
It should be noted that the memory of the UNICODE_STRING
structure created by the RtlCreateUnicodeStringFromAsciiz
function needs to be released manually, otherwise memory leaks will occur. You can use the RtlFreeUnicodeString
function to release this memory. The function prototype is as follows:
NTSYSAPI VOID RtlFreeUnicodeString( PUNICODE_STRING UnicodeString );
This function accepts a UNICODE_STRING
structure pointer, which is used to specify the structure that needs to release memory.
The second method is implemented through transfer. First, the user can use RtlInitString
to initialize a CHAR*
into an ANSI structure, and then use RtlAnsiStringToUnicodeString
code>Complete the type conversion from ANSI
to UNICODE
in one go;
#define _CRT_SECURE_NO_WARNINGS #include <ntifs.h> #include <windef.h> #include <ntstrsafe.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstallation successful\\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \\ "); UNICODE_STRING uncode_buffer_source = {<!-- --> 0 }; ANSI_STRING ansi_buffer_target = {<!-- --> 0 }; //Set CHAR* char szBuf[1024] = {<!-- --> 0 }; strcpy(szBuf, "hello lyshark"); //Initialize ANSI string RtlInitString( & amp;ansi_buffer_target, szBuf); // Conversion function NTSTATUS flag = RtlAnsiStringToUnicodeString( & amp;uncode_buffer_source, & amp;ansi_buffer_target, TRUE); if (NT_SUCCESS(flag)) {<!-- --> DbgPrint("UNICODE: %wZ \\ ", & amp;uncode_buffer_source); } // Destroy UNICODE string RtlFreeUnicodeString( & amp;uncode_buffer_source); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is as shown below:
2.3.4 String concatenation operation
Strings can also be concatenated, such as merging strings in two different variables to generate a new string. The concatenation can be achieved through the kernel function RtlAppendUnicodeToString
.
RtlAppendUnicodeToString
is used to append a Unicode
string to the end of another Unicode
string. This function is located in ntdll.dll
and can be linked through the NtDll.lib
library. The prototype of the function is as follows:
NTSTATUS RtlAppendUnicodeToString( PUNICODE_STRING DestinationString, PCWSTR SourceString );
Among them, DestinationString
is a pointer to the UNICODE_STRING
structure of the destination string, and SourceString
is a pointing to the source string. Pointer of type wchar_t
.
Using this function you can easily concatenate two strings by passing the first string as the DestinationString
parameter and the second string as the SourceString
parameter Just pass it on. This function will automatically calculate the length of the two strings and append the contents of the second string to the end of the first string.
The following is an example code that concatenates two strings str1
and str2
and outputs the result:
#include <ntifs.h> VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("The driver has been uninstalled \\ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \\ "); UNICODE_STRING dst; WCHAR dst_buf[256]; NTSTATUS status; //Initialize string UNICODE_STRING src = RTL_CONSTANT_STRING(L"hello"); // The string is initialized to an empty string with a length of 256 RtlInitEmptyUnicodeString( & amp;dst, dst_buf, 256 * sizeof(WCHAR)); //Copy src to dst RtlCopyUnicodeString( & amp;dst, & amp;src); // Append after dst status = RtlAppendUnicodeToString( & amp;dst, L" lyshark"); if (status == STATUS_SUCCESS) {<!-- --> DbgPrint("Output the linked string: %wZ \\ ", & amp;dst); } Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
Finally, we use the DbgPrint
function to output the result. Before outputting the result, we need to use the %wZ
formatting symbol to output the Unicode
string as a parameter.