Windows驱动开发学习记录-内核模式下字符串后缀匹配

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))
{
    ...
}

一:SSDT表的hook检测和恢复 ~!~~~ 二:IDT表的hook检测和恢复 ~~~~~~(idt多处理器的恢复没处理,自己机器是单核的,没得搞,不过多核的列举可以) 三:系统加载驱动模块的检测 通过使用一个全局hash表(以DRIVEROBJECT为对象)来使用以下的方法来存储得到的结果,最终显示出来 1.常规的ZwQuerySystemInformation来列举 2通过打开驱动对象目录来列举 3搜索内核空间匹配驱动的特征来列举(这个功能里面我自己的主机一运行就死机,别的机器都没事,手动设置热键来蓝屏都不行,没dump没法分析,哎,郁闷) 4从本驱动的Modulelist开始遍历来列举驱动 四:进程的列举和进程所加载的dll检测 采用以下方法来列举进程: 1ZwQuerySystemInformation参数SystemProcessesAndThreadsInformation来枚举 2进程EPROCESS 结构的Activelist遍历来枚举 3通过解析句柄表来枚举进程 4通过Handletablelisthead枚举进程 5进程创建时都会向csrss来注册,从这个进程里面句柄表来枚举进程 6通过自身进程的HANDLETABLE来枚举进程 7通过EPROCESS的SessionProcessLinks来枚举进程 8通过EPROCESS ---VM---WorkingSetExpansionLinks获取进程 9暴力搜索内存MmSystemRangeStart以上查找PROCESS对象 进程操作: 进程的唤醒和暂停通过获取PsSuspendProcess和PsResumeProcess来操作的 进程结束通过进程空间清0和插入apc。 采用以下方法查找DLL: 1遍历VAD来查找dll 2挂靠到对应的进程查找InLoadOrderLinks来枚举dll 3暴力搜索对应进程空间查找pe特征来枚举dll DLL的操作: Dll的卸载是通过MmUnmapViewOfSection和MmmapViewOfSection(从sdt表中相应函数搜索到的)来实现的(本来想直接清0 dll空间,有时行有时不行)(只要将这个进程的ntdll卸载了,进程就结束了,一个好的杀进程的办法撒,绿色环保无污染),注入dll使用的是插入apc实现的。(注入的dll必须是realse版的。Debug版会出现***错误,全局dll注入貌似也是)插入apc效果不是很好,要有线程有告警状态才执行。 五:线程信息的检测 遍历ThreadList来枚举线程 线程的暂停和唤醒都是通过反汇编获取PsResumeThread和PsSuspendThread直接从r3传来ETHREAD来操作的,通过插入APC来结束线程 六:shadow sdt表的hook检测与恢复 没有采用pdb来解决函数名问题,直接写入xp和03的shandow表函数名(主要是自己的网不稳定,连windbg有时都连不上微软) 七:系统所有的过滤驱动的检测 查看各device下是否挂接有驱动之类的,可直接卸载 八:系统常用回调历程的检测和清除 只检查了PsSetLoadImageNotifyRoutine PsSetCreateThreadNotifyRoutine PsSetCreateProcessNotifyRoutine CmRegisterCallback这几个,至于那个什么shutdown回调不知道是啥玩意,就没搞了,有知道的顺便告诉我下撒,谢谢 九:文件系统fat和ntfs的分发函数检测 直接反汇编fat和ntfs的DriverEntry得到对应的填充分发的偏移,然后和当前已经运行的文件系统的分发相比是否被hook,并附带恢复 十:文件查看功能 自己解析ntfs和fat的结构,来实现列举文件和直接写磁盘删除。附带有普通的删除和发生IRP来删除。不过这里面有点问题,ntfs删除有时把目录给搞坏了,大家凑合着吧, Ntfs网上删除这些操作的代码不多,就是sudami大大的利用ntfs-3g来实现的,看了下,太多了,充满了结构。然后自己对照着系统删除文件时目录的变化来自己实现的。只处理了$BITMAP对应的位清除,父目录的对应文件的索引项的覆盖,删除文件对应的filerecord清0. 另外偷懒时间都没处理,呵呵,y的,一个破时间都都搞好几个字节移来移去的。 十一:常用内核模块钩子的检测和恢复 这里只检测了主要的内核模块nkrnlpa**.exe的.win32k.sys,hal.dll,比对它们的原始文件从而查找eat inline hook,iat hook ,和inline hook。Inline是从TEXT段开始一段位置开始比较的。(有点慢貌似,等待显示扫描完成就好了) 十二:应用层进程所加载dll的钩子 应用层钩子检测和内核模块钩子检测原理一样,不过为了能读写别的进程的空间,并没有使用openprocess去打开进程,而是通过KiattachProcess挂靠到当前进程,然后通过在r0直接读写进程空间的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值