您的位置:首页 > 其它

驱动开发心得经验和想法

2014-11-02 15:07 936 查看
这里记载一些心得经验和想法(没有实验).

这里大多是血的教训!请大家谨记.

1.ExAllocatePoolWithTag不但检查成功否和释放ExFreePoolWithTag,更重要的是要RtlZeroMemory,不然会有乱码等奇怪的现象.最好立马使用,最近使用.

不会C++,没有C++的思想。不过写下面的两个函数倒有进步的思想。

#define TAG 'tset' //驱动在内存的标志,即test

//new delete

PVOID allocate(IN SIZE_T NumberOfBytes)
/*
不建议对申请内存函数的封装,这样对内存泄漏不好定位.
*/
{
PVOID p = NULL;

//PAGED_CODE();
if (KeGetCurrentIrql() > DISPATCH_LEVEL)
{
KdBreakPoint();//DbgBreakPoint()
}

/*
Callers of ExAllocatePoolWithTag must be executing at IRQL <= DISPATCH_LEVEL.
A caller executing at DISPATCH_LEVEL must specify a NonPagedXxx value for PoolType.
A caller executing at IRQL <= APC_LEVEL can specify any POOL_TYPE value, but the IRQL and environment must also be considered for determining the page type.
*/

p = ExAllocatePoolWithTag(NonPagedPool, NumberOfBytes, TAG);
if (p == NULL ) {
return p;
}

/*
Warning  Memory that ExAllocatePoolWithTag allocates is uninitialized.
A kernel-mode driver must first zero this memory if it is going to make it visible to user-mode software (to avoid leaking potentially privileged contents).
*/

RtlZeroMemory(p, NumberOfBytes);

return p;
}

VOID free(IN PVOID p)
{
unsigned long r;

/*
Callers of ExFreePoolWithTag must be running at IRQL <= DISPATCH_LEVEL.
A caller at DISPATCH_LEVEL must have specified a NonPagedXxx PoolType when the memory was allocated.
Otherwise, the caller must be running at IRQL <= APC_LEVEL.
*/

//PAGED_CODE();

if (KeGetCurrentIrql() > DISPATCH_LEVEL)
{
KdBreakPoint();//DbgBreakPoint()
}

if (p) //防止多次释放导致的蓝屏。
{
__try //防止传入非法的地址。
{
r = MmIsAddressValid(p);
ExFreePoolWithTag(p, TAG);//KeGetCurrentIrql() > DISPATCH_LEVEL时依旧蓝屏。
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
r = GetExceptionCode();//啥也不做。
}

p = NULL;
}
}

2.不可用BOOL与true或者TRUE比较,因为:typedef int BOOL;
所以:
BOOL b = PathIsDirectory(buffer);
//if (b == true) //00B4161A  cmp         dword ptr [ebp-268h],1
//if (b == TRUE) //00B4161A  cmp         dword ptr [ebp-268h],1
if (b)           //cmp         dword ptr [ebp-268h],0

3.要对用:RtlAppendUnicodeStringToString或者RtlAppendUnicodeToString或者RtlAppendStringToString对STRING或者UNICODE_STRING追加字符,原有字符结构必须有内存,不能是初始化的.
就是初始化的时候要使用:RtlInitEmptyUnicodeString,而不能使用:RtlInitUnicodeString(&us1,L"\\REGISTRY\\USER\\");

系统生成的UNICODE_STRING,如文件对象(FileObject)里面的,这是最好传递UNICODE_STRING指针,前提是不能修改这个输入参数,如要修改请备份或者复制一份.
如果传递字符串的地址,再用RtlInitUnicodeString初始化,不仅麻烦而且易出错,因为:字符串的地址没有结束标志,且字符串后面的地址有可能不可以访问,所以会蓝屏.
如果非要传递字符串的地址,建议一定加上字符串的长度,用原始的方法初始UNICODE_STRING,不要用RtlInitUnicodeString了.

4.FltRegisterFilter函数返回STATUS_OBJECT_NAME_NOT_FOUND
#define STATUS_OBJECT_NAME_NOT_FOUND     ((NTSTATUS)0xC0000034L)
原因是注册表中没有如下内容:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\xxxxxx\Instances]
"DefaultInstance"="xxxxxx"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\xxxxxx\Instances\xxxxxx]
"Altitude"="371100"
"Flags"=dword:00000000

并注意:Altitude的值和注册时要一致.

说明:必须有一个名为"Instances"的子项用于存放驱动的实例信息,该子项下面的字符串值"DefaultInstance"指定了默认实例的名称。
"Instances"项下面的每一个子项表示一个实例,每个实例子项必须有一个字符串值"Altitude"。
FltRegisterFilter函数执行时,如果在注册表中没有找到默认实例的"Altitude"值,将会返回STATUS_OBJECT_NAME_NOT_FOUND错误。

5.ObRegisterCallbacks返回STATUS_ACCESS_DENIED (0xc0000022)
解决办法:
首选办法:sources里面加入:LINKER_FLAGS = $(LINKER_FLAGS)/INTEGRITYCHECK
第二个方案: 注册回调前加段代码,改一个比特位就解决了
代码:
PLDR_DATA_TABLE_ENTRY pLdrEntry=(PLDR_DATA_TABLE_ENTRY)pDrvObj->DriverSection;
pLdrEntry->Flags |=0x20;

6.如果有两个桌面,在内核中判断或者区分?
1.进程回调加父子关系。我是用链表实现了。
2.未公开的函数,详细的请查询http://doxygen.reactos.org。
3.判断进程内的某个类型的对象的值,即:DeskTop。
4.对象里面有个结构,就是使用这个对象的所有的进程的链表。

7.判断一个文件或者文件夹是不是在另一个文件夹里面。
应用层和驱动层通吃,这是一个方法和思路。
1.判断是不是这个文件夹,之前最好判断一下是不是目录。
2.如果待判断的文件或者目录的长度大于特定的目录,取代判断的目录或者文件夹的长度加一,然后和特定的目录加"\\"比较。
如果是miniFilter,有更方便的办法,因为它解析好了。
另外内核还有一些特殊的函数,至少三类:
1.以str,wcs,_wcs开头的函数。由内核导出,但是不建议使用,因为好多字符串不是以0x00结尾的.
2.刚开始还以为是:RtlLeftChild呢?后来发现了RtlPrefixUnicodeString,实验成功。不过要调用两次,后一次加一个\.
3.FsRtlIsNameInExpression或者FsRtlIsDbcsInExpression等。这个实验始终失败.

8.KeSetEvent使用的一个要点.
LONG KeSetEvent(IN PRKEVENT  Event, IN KPRIORITY  Increment, IN BOOLEAN  Wait);
最后一个参数为真,必须马上调用等待函数,不然蓝屏,注意IRQL还会升高,具体的看说明.
这是加班到凌晨4点才解决,后来才明白的.

9.UserMode or KernelMode
很多函数的说明中有这么一句话:Lower-level drivers should specify KernelMode.
实际要怎么做?其实大多说是UserMode.只有系统进程或者驱动专用的是KernelMode.
其实完美的办法是调用ObIsKernelHandle函数.ObOpenObjectByPointer这个函数有点麻烦.
彻底的解决办法是ExGetPreviousMode(VOID).

10.由于VS2013没有一键转换的功能,所以依旧用vs2012,但是要编译:Windows Driver Kit (WDK) 8.1 Preview Samples下的例子,要把工程的编译平台修改为8.0(WindowsKernelModeDriver8.0)即可,注意有两处.不然编译出错.
error MSB8020: The builds tools for WindowsKernelModeDriver8.1 (Platform Toolset = 'WindowsKernelModeDriver8.1') cannot be found. To build using the WindowsKernelModeDriver8.1 build tools, either click the Project menu or right-click the solution, and then select "Update VC++ Projects...". Install WindowsKernelModeDriver8.1 to build using the WindowsKernelModeDriver8.1 build tools.

11.数字签名.
这个我也不太懂,有的要5级签名,微软的在最上等.
今天遇到一个,即使签名了也会出现:577 = 0x241 .
其含义为:Windows 无法验证此文件的数字签名。某软件或硬件最近有所更改,可能安装了签名错误或损毁的文件,或者安装的文件可能是来路不明的恶意软件。
改进办法是用命令行签名,最好加上时间信息,不过这又要开发环境了.

12.minifilter在卷挂载(PFLT_INSTANCE_SETUP_CALLBACK)的时候,获取卷设备的一些信息,更多信息请自己扩展.
BOOLEAN PrintVolume(__in PCFLT_RELATED_OBJECTS FltObjects)
/*
功能:打印挂载的卷的信息。
*/
{
NTSTATUS status;
PVOID Buffer;
BOOLEAN r = FALSE;
ULONG BufferSizeNeeded;
UNICODE_STRING Volume;

status = FltGetVolumeName(FltObjects->Volume, NULL, &BufferSizeNeeded);
if (status != STATUS_BUFFER_TOO_SMALL) {
return FALSE;
}

Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSizeNeeded + 2, TAG);
if (Buffer == NULL) {
return FALSE;
}
RtlZeroMemory(Buffer,BufferSizeNeeded + 2);

Volume.Buffer = Buffer;
Volume.Length = (USHORT)BufferSizeNeeded;
Volume.MaximumLength = (USHORT)BufferSizeNeeded + 2;

status = FltGetVolumeName(FltObjects->Volume, &Volume, &BufferSizeNeeded);//最后一个参数为NULL失败。
if (!NT_SUCCESS(status)) {
KdPrint(("FltGetVolumeName fail with error 0x%x!\n",status));
ExFreePoolWithTag(Buffer, TAG);
return FALSE;
}

KdPrint(("挂载的卷为:%wZ\n",&Volume));

ExFreePoolWithTag(Buffer, TAG);
return r;
}

打印信息有:
挂载的卷为:\Device\Mup
挂载的卷为:\Device\HarddiskVolume1
挂载的卷为:\Device\HarddiskVolume2
挂载的卷为:\Device\HarddiskVolume4
挂载的卷为:\Device\HarddiskVolume3
挂载的卷为:\Device\HarddiskVolume5
挂载的卷为:\Device\CdRom0

13.
BOOLEAN IsMyVolume(__in PCFLT_RELATED_OBJECTS FltObjects)
{
BOOLEAN b = FALSE;
PFILE_OBJECT    FileObject;
UNICODE_STRING  uni_disk;
UNICODE_STRING  Hide_name;
NTSTATUS status = STATUS_SUCCESS;

FileObject = FltObjects->FileObject;//sp->FileObject;

//On Windows Vista and later operating systems, you must ensure that APCs are not disabled before calling this routine.
//Call KeAreAllApcsDisabled for this purpose.

//ObReferenceObjectByPointer((PVOID)FileObject,0,NULL,KernelMode);
status = IoVolumeDeviceToDosName(FileObject->DeviceObject,&uni_disk);//RtlVolumeDeviceToDosName 支持xp之前,但是prefast警告.
//ObDereferenceObject(FileObject);

if (!NT_SUCCESS( status )) //如果失败了puni_disk->buffer == 0,下面的比较会蓝屏.
{
return b;
}

RtlInitUnicodeString(&Hide_name,L"X:");

if (RtlEqualUnicodeString(&Hide_name,&uni_disk,TRUE))
{
b = TRUE;
}

ExFreePool(uni_disk.Buffer);//或者下面的办法.
//RtlFreeUnicodeString(&uni_disk);

return b;
}

14.OACR的使用.
正常情况下,编译之后,双击图标即可显示.
但是非正常情况下:
1.编译驱动
2.check now -> 选项.
3.view warnings  -> 选项.
以上是个人理解,并非正确.

15.C和CPP与布尔变量的关系。
C中默认情况下只能使用大写的布尔变量。
C++中注意这是两种不同的数据类型。

在C中测试效果如下:
BOOLEAN BX;//BYTE
//BOOL B;//int
//bool b;

BOOLEAN BX1 = TRUE;
//BOOLEAN BX2 = true;
注意注释的是错误的,不过在CPP中是都可以的。
谨记,这是一会开发驱动程序,一会写应用层代码所得的。

16.再论UNICODE_STRING。

//
// Unicode strings are counted 16-bit character strings. If they are
// NULL terminated, Length does not include trailing NULL.
//

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
_Field_size_bytes_part_(MaximumLength, Length) PWCH   Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
//上面是文件中的定义。

//下面是文档中的说明。
typedef struct _UNICODE_STRING {
USHORT  Length;
USHORT  MaximumLength;
PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

Length
The length in bytes of the string stored in Buffer.
MaximumLength
The length in bytes of Buffer.
Buffer
Pointer to a buffer used to contain a string of wide characters.

If the string is NULL-terminated, Length does not include the trailing NULL.

个人理解:
上面的必须看懂并记住。
尽管微软所这是安全的字符串,不会出所谓的问题。
但是使用不当还是会出现蓝屏和莫名奇怪的问题。
因为好像没有函数检验字符串的有效性。
至少在WINDBG的本地变量里面显示的字符串和长度是可以不相符的。
反过来说,理解了这个结构,可以写出一些技巧的代码。
再次重复,长度是字节的长度,
所以在内存地址中定位字符的时候要除以Buffer的单位再减一。
所以复制的时候千万不要长度再乘以Buffer的单位。
这些问题很难发现和定位,费了我两天的时间,才解决,所以写此心得。

17.看《windows内核情景分析》的9.12MDL章节:
更喜欢叫DeviceObject->Flags的:
DO_BUFFERED_IO为复制方式,特点:系统申请非分页内存,然后再复制。
DO_DIRECT_IO为映射方式,特点:获取用户地址的物理地址的内核地址。
neither buffered nor direct I/O为直接方式,特点:很少使用或者直接使用,注意DPC/ISR中不可以用。
更多的官方信息: http://msdn.microsoft.com/en-us/library/windows/hardware/ff550869(v=vs.85).aspx Neither I/O Operations http://msdn.microsoft.com/en-us/library/windows/hardware/ff565381(v=vs.85).aspx Using Direct I/O with PIO http://msdn.microsoft.com/en-us/library/windows/hardware/ff565374(v=vs.85).aspx Using Direct I/O with DMA http://msdn.microsoft.com/en-us/library/windows/hardware/ff565356(v=vs.85).aspx Using Buffered I/O
具体的例子可看:\7600.16385.1\src\general\ioctl\wdm\sys。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: