1、前提环境
最近系统学习驱动开发,想做一个基本的系统加载驱动过滤,其中遇到字符串匹配的问题,记录一下。
1.1、使用PsSetLoadImageNotifyRoutine创建回调
NTKERNELAPI
NTSTATUS
PsSetLoadImageNotifyRoutine(
_In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
1.2、回调函数
void MyloadImageNotifyRoutine(
PUNICODE_STRING FullImageName,
HANDLE ProcessId,
PIMAGE_INFO ImageInfo)
{
......
}
回调函数中FullImageName为加载的PE文件全路径,打算以此来进行判断,因为系统加载的PE文件不仅有.sys文件,还包括.exe、.dll等文件,所以在此通过判断FullImageName的全路径扩展名是否为.sys再来进行操作,问题也就在此产生。
2、字符串后缀匹配
2.1、匹配思路
在本人的测试环境中加载的驱动全路径为"C:\Users\Administrator\Desktop\HelloDriver.sys",因此可以通过判断MyloadImageNotifyRoutine中该UNICODE_STRING后四位是否为.sys来确定加载的是否为驱动。但写代码过程中一些细节导致失败。
2.2、使用PCWSTR指针+RtlInitUnicodeString中的错误
代码如下
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
其中关键的一行为PCWSTR pBuffer = FullImageName->Buffer + FullImageName->Length - sizeof(WCHAR) * 4,逻辑是取FullImageName的Buffer起址加上偏移,因为UNICODE_STRING的Length字段为总长度,单位是字节而非字符串长度,所以 +FullImageName->Length - sizeof(WCHAR) * 4之后应该为后四个字符的起址。但这里有问题,FullImageName->Buffer为wchar_t*类型,FullImageName->Buffer + FullImageName->Length之后地址远超Buffer范围了,正确的写法为PCWSTR pBuffer = (PUCHAR)FullImageName->Buffer + FullImageName->Length - sizeof(WCHAR) * 4,哪想会犯这种低级错误。
2.2.2
即使不犯2.2.1错误,代码仍然不正确,原因再于通过PCWSTR pBuffer = (PUCHAR)FullImageName->Buffer + FullImageName->Length - sizeof(WCHAR) * 4 地址后,该地址对应的并不是以L'\0'结尾的字符串,因为UNICODE_STRING并不是以L'\0'来确定结束的,而是根据Length字段,所以之后的RtlInitUnicodeString并不能正确初始化字符串。所以需要更改思路。
2.3、使用WCHAR数组+RtlInitUnicodeString中的错误
接下来换思路,定义一个WCHAR数组,然后将FullImageName->Buffer中后四位复制到数组中再初始化字符串,代码如下:
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
此处仍犯了个错误类似2.2.2,从FullImageName->Buffer复制到szBuffer中时为四个字符,但szBuffer也只有四个宽字符的大小,故没有在结尾放L'\0',所以WCHAR szBuffer[4] = { 0 },该处应该改为WCHAR szBuffer[5] = { 0 };
3、最终代码
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[5] = { 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、其它思考
4.1 关于比较
当前比较的方法时先生成一个".sys"的字符串,然后将FullImageName 字符串截取最后四个字符并生成新的UNICODE_STRING,然后通过RtlEqualUnicodeString进行比较。
能否直接使用RtlCompareMemory或者memcmp来进行判断,这里本人并未进行实验,主要考虑到RtlEqualUnicodeString可以指定大小写是否敏感,在大小 写都可能存在的情况下用直接内存比较不是最佳方案。
4.2 关于判断.sys文件加载
上述方法是使用加载映像名称扩展名是否为.sys来判断,感觉一来效率不高,二来如果加载的驱动扩展名不为.sys则实现不了。查看了MSDN中关于MyloadImageNotifyRoutine回调的第三个参数:
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.
指出如果是内核组件加载例如驱动之类的这个字段就设值1,否为0。因此可以通过这字段为1判断加载的是内核驱动。
代码为:
void MyloadImageNotifyRoutine(
PUNICODE_STRING FullImageName,
HANDLE ProcessId,
PIMAGE_INFO ImageInfo)
{
if (ImageInfo && ImageInfo->SystemModeImage)
{
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "MyloadImageNotifyRoutine:%wZ", FullImageName));
//do something else
}
}
4.3 关于比较(2021/08/29)
后来发现比较字符串内核中有提供一个函数叫FsRtlIsNameInExpression,原型如下:
BOOLEAN FsRtlIsNameInExpression(
PUNICODE_STRING Expression,
PUNICODE_STRING Name,
BOOLEAN IgnoreCase,
PWCH UpcaseTable
);
详细说明见MSDN,这样匹配字符串是否为.sys结尾就可以用以下代码,非常方便
UNICODE_STRING usPattern; //这个就是需要检查是否满足一定规则的那个字串
RtlInitUnicodeString(&usPattern, L"*.SYS");
if (FsRtlIsNameInExpression(&usPattern, FullImageName, true, NULL))
{
...
}