Windows driver development learning record - string suffix matching in kernel mode

Posted by elindithas on Sun, 19 Dec 2021 19:35:07 +0100

1. Premise environment

Recently, the system learning driver development, want to do a basic system loading driver filtering, which encountered the problem of string matching, record it.

1.1. Create callback using PsSetLoadImageNotifyRoutine

NTKERNELAPI
NTSTATUS
PsSetLoadImageNotifyRoutine(
    _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);

1.2 callback function

void MyloadImageNotifyRoutine(
	PUNICODE_STRING FullImageName,
	HANDLE ProcessId,
	PIMAGE_INFO ImageInfo)
{
    ......
}

FullImageName in the callback function is the full path of the loaded PE file, which is intended to be used for judgment, because the PE file loaded by the system not only has sys file, including exe,. dll and other files, so judge whether the full path extension of FullImageName is sys will operate again, and the problem will arise here.

2. String suffix match

2.1 matching ideas

The full path of the driver loaded in my test environment is "C:\Users\Administrator\Desktop\HelloDriver.sys", so you can judge the Unicode in MyloadImageNotifyRoutine_ Whether the last four digits of string are sys to determine whether the loaded is a driver. But some details in the process of writing code lead to failure.

2.2. Error in using pcwstr + rtlininitunicode string

The code is as follows

void MyloadImageNotifyRoutine(
	PUNICODE_STRING FullImageName,
	HANDLE ProcessId,
	PIMAGE_INFO ImageInfo)
{
    UNICODE_STRING usExtBuffer = RTL_CONSTANT_STRING(L".sys");
    UNICODE_STRING usImageExtBuffer = { 0 };
    PCWSTR pBuffer = FullImageName->Buffer + FullImageName->Length - sizeof(WCHAR) * 4;
	RtlInitUnicodeString(&usImageExtBuffer, pBuffer);
	if (RtlEqualUnicodeString(&usExtBuffer, &usImageExtBuffer, true))
	{
		KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "MyloadImageNotifyRoutine:%wZ", FullImageName));
	}
}

2.2.1

One of the key behaviors is pcwstr Pbuffer = FullImageName - > Buffer + FullImageName - > Length - sizeof (wchar) * 4. The logic is to take the Buffer address of FullImageName plus offset, because Unicode_ The Length field of string is the total Length, and the unit is bytes rather than the string Length. Therefore, the address of the last four characters should be after + FullImageName - > Length - sizeof (wchar) * 4. But there is a problem here. FullImageName - > Buffer is wchar_t * type. After FullImageName - > Buffer + FullImageName - > Length, the address is far beyond the Buffer range. The correct writing is pcwstr Pbuffer = (Puchar) FullImageName - > Buffer + FullImageName - > Length - sizeof (wchar) * 4. I don't think this low-level error will be made.

2.2.2

Even if you don't make 2.2 1 error. The code is still incorrect. The reason is that after passing the pcwstr Pbuffer = (Puchar) fullimagename - > buffer + fullimagename - > Length - sizeof (wchar) * 4 address, the address does not correspond to a string ending in L' L '\ 0'', because UNICODE_STRING does not end with L' L '\ 0'', but according to the Length field, so the subsequent rtlininitunicode string cannot initialize the string correctly. So we need to change our thinking.

2.3. Error in using WCHAR array + rtlininitunicode string

Next, define a WCHAR array, copy the last four bits in fullimagename - > buffer to the array, and then initialize the string. The code is as follows:

void MyloadImageNotifyRoutine(
    PUNICODE_STRING FullImageName,
    HANDLE ProcessId,
    PIMAGE_INFO ImageInfo)
{
    if (FullImageName && FullImageName->Length >= sizeof(WCHAR) * 4)
	{
		UNICODE_STRING usExtBuffer = RTL_CONSTANT_STRING(L".sys");
		UNICODE_STRING usImageExtBuffer = { 0 };
		WCHAR szBuffer[4] = { 0 };
		PUCHAR pBuffer = (PUCHAR)FullImageName->Buffer + FullImageName->Length - sizeof(WCHAR) * 4;
		RtlCopyMemory(szBuffer, pBuffer, sizeof(WCHAR) * 4);
		RtlInitUnicodeString(&usImageExtBuffer, szBuffer);
		if (RtlEqualUnicodeString(&usExtBuffer, &usImageExtBuffer, true))
		{
			KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "MyloadImageNotifyRoutine:%wZ", FullImageName));
		}
	}
}

2.3.1

There is still a mistake like 2.2 2. When copying from fullimagename - > buffer to szBuffer, it is four characters, but szBuffer is only four wide characters, so L' l '\ 0'' is not placed at the end, so wchar szBuffer [4] = {0}, which should be changed to wchar szBuffer [5] = {0};

3. Final code

void MyloadImageNotifyRoutine(
	PUNICODE_STRING FullImageName,
	HANDLE ProcessId,
	PIMAGE_INFO ImageInfo)
{
	if (FullImageName && FullImageName->Length >= sizeof(WCHAR)*4) 
	{
		
		UNICODE_STRING usExtBuffer = RTL_CONSTANT_STRING(L".sys");
		UNICODE_STRING usImageExtBuffer = { 0 };
		WCHAR szBuffer[4] = { 0 };
		PUCHAR pBuffer = (PUCHAR)FullImageName->Buffer + FullImageName->Length - sizeof(WCHAR)*4;
		RtlCopyMemory(szBuffer, pBuffer, sizeof(WCHAR) * 4);
		RtlInitUnicodeString(&usImageExtBuffer, szBuffer);
		if (RtlEqualUnicodeString(&usExtBuffer, &usImageExtBuffer, true))
		{
			KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "MyloadImageNotifyRoutine:%wZ", FullImageName));
            ......//do something else...
		}
	}
}

4. Other thinking

4.1} about comparison

The current comparison method is converted into a ". sys" string, and then the last four characters of FullImageName string are intercepted and a new Unicode is generated_ String, and then compare it with rtlequalincodestring.

Whether RtlCompareMemory or memcmp can be directly used for judgment, I have not conducted experiments here, mainly considering that rtlequalnicodestring can specify whether case is sensitive. When both case and case may exist, direct memory comparison is not the best scheme.

4.2 , about judgment sys file loading

The above method is to use the load image name and whether the extension is sys to judge, I feel that the efficiency is not high, and if the loaded driver extension is not sys cannot be implemented. Viewed the third parameter in MSDN about the MyloadImageNotifyRoutine callback:

typedef struct _IMAGE_INFO {
  union {
    ULONG Properties;
    struct {
      ULONG ImageAddressingMode : 8;
      ULONG SystemModeImage : 1;
      ULONG ImageMappedToAllPids : 1;
      ULONG ExtendedInfoPresent : 1;
      ULONG MachineTypeMismatch : 1;
      ULONG ImageSignatureLevel : 4;
      ULONG ImageSignatureType : 3;
      ULONG ImagePartialMap : 1;
      ULONG Reserved : 12;
    };
  };
  PVOID  ImageBase;
  ULONG  ImageSelector;
  SIZE_T ImageSize;
  ULONG  ImageSectionNumber;
} IMAGE_INFO, *PIMAGE_INFO;

SystemModeImage

Set either to one for newly loaded kernel-mode components, such as drivers, or to zero for images that are mapped into user space.

Point out that if the kernel component loads, such as driver, this field is set to 1, and no is 0. Therefore, it can be judged that the kernel driver is loaded by setting 1 in this field.

Topics: C++ Windows visualstudio