2.3 Windows driver development: kernel string conversion method

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 ANSIMulti-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 and UNICODE_STRING, which are used to store strings before and after conversion respectively;
  • 2. Call the RtlUnicodeStringToAnsiString function to convert UNICODE_STRING to the ANSI_STRING type;
  • 3. Define a variable of type CHAR* to store the converted string;
  • 4. Convert the ANSI_STRING type to the CHAR* type. You can use the character array pointed to by ANSI_STRING.Buffer as CHAR*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.

syntaxbug.com © 2021 All Rights Reserved.