一个串口过滤驱动的解释及分析
转自杀进程网安全实验室
我又回来了亲们
今天我给大家带来的不是一个程序 而是一个驱动程序的源码的分析
这是入口处函数
他会接收一个驱动对象 和他在注册表的全路径
返回的是加载状态
返回STATUS_SUCCESS说明是加载成功
从这一段看出 这个驱动程序是将所有的动作都传递到HelloDDKDispatchRoutine这个函数了
并且单独为卸载做了一个函数 这说明这个驱动程序应该是可以安全卸载的
这里出现了两个自定函数 一个是 OpenCom 从这里看出 这个函数的参数是 一个用来计数的数值和一个状态值
返回的是一个驱动对象的指针
当这个指针为空的时候 则忽略后面的代码 直接执行下一次循环
现在我们看看OpenCom这个函数
这里先初始化了一块内存 大小为32字节
然后使用RtlStringCchPrintfW初始化一个字符串 值是\\Device\\Serial跟输入的计数用的参数
然后使用IoGetDeviceObjectPointer来获取设备对象
因为IoGetDeviceObjectPointer会增加这个设备对象的引用计数 所以如果成功 就用ObDereferenceObject将引用计数减一
然后返回这个设备对象
然后是上面出现的第二个函数 也就是AttachDevice
这里他生成了一个虚拟设备
如果成功失败 则返回失败代码
这一段代码 将新生成的设备 赋予了和即将被绑定的设备一样的属性
然后调用IoAttachDeviceToDeviceStack将生成的虚拟设备绑定到原驱动上
如果失败 则删除生成的设备 然后返回未成功
如果绑定成功 则将传递进来的缓冲区的值设置成绑定成功的设备对象
然后将驱动状态清除DO_DEVICE_INITIALIZING以设置为启用
这里说一下
对于驱动程序在其 DriverEntry 例程中创建的任何设备对象,由 I/O 管理器负责清除 DO_DEVICE_INITIALIZING。对于除 DriverEntry 之外的任何例程中创建的任何设备对象,由驱动程序负责清除 DO_DEVICE_INITIALIZING
而DO_DEVICE_INITIALIZING 的目的是防止其它组件在驱动程序完成初始化设备对象之前向设备发送 I/O。
然后返回成功
这里说一下 之前代码中的常量
值是自定的 这里是32 因为认为一个机器上不至于有32个以上的串口..= =!
至此绑定完成了
那么我们看看他是怎么处理动作的
这里?
大家记得之前的
么?
这个函数将设备对象保存在fltDevObj这个数组中了哦
所以循环比对是否成功绑定过这个设备
如果绑定过 再执行内部的代码
那么 没绑定过呢?
没绑定过 就认为没这个设备 嘿嘿 就不管它了
因为32已经是个很大的数了不是么
保险起见我建议大家用128 虽然可能加载效率会降低些..但是没什么感觉的
函数中 发现 这个irp的操作是一个IRP_MJ_POWER 也就是电源操作的
调用PoStartNextPowerIrp来继续这个操作
然后IoSkipCurrentIrpStackLocation挂起这个irp
直接调用PoCallDriver把一个主操作为IRP_MJ_POWER的irp传递给之前保存的对应的真实设备去咯
当IRP的主操作为写入 也就是IRP_MJ_WRITE时??将被解析
首先将写入的长度保存下来
然后初始化一个指针参数
只要写入的mdl地址不是空
那么就
MmGetSystemAddressForMdlSafe 的方式将这个mdl的物理地址获取出来
如果是空 那么就获取他的UserBuffer的地址
但是用户地址还有可能是空 并且MmGetSystemAddressForMdlSafe也有可能失败
所以还需要校验一边
如果是空 则取得
Irp->AssociatedIrp.SystemBuffer
然后循环输出写入的数据
然后 因为这里的过滤是截获到之后 不需要向下发送
所以直接返回已经处理完成
然后返回STATUS_SUCCESS
然后是卸载函数 这个函数一般不该提供 因为正常是重启卸载
但是作者这里提供了
这里我就不解释了 因为注释已经说的很清楚了
今天我给大家带来的不是一个程序 而是一个驱动程序的源码的分析
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject ,PUNICODE_STRING pRegistryPath)
{
NTSTATUS status,statuscom;
ULONG i;
PDEVICE_OBJECT pComDev;
KdPrint(("Enter DriverEntry!\n"));
size_t i = 0;
这是入口处函数
他会接收一个驱动对象 和他在注册表的全路径
返回的是加载状态
返回STATUS_SUCCESS说明是加载成功
for (i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION ; i++)
{
pDriverObject->MajorFunction = HelloDDKDispatchRoutine;
}
pDriverObject->DriverUnload = HelloDDKUnload;
从这一段看出 这个驱动程序是将所有的动作都传递到HelloDDKDispatchRoutine这个函数了
并且单独为卸载做了一个函数 这说明这个驱动程序应该是可以安全卸载的
for (i = 0 ; i < MAX_COM_NUM ; i++)
{
pComDev = OpenCom(i , &status);
if (pComDev == NULL)
{
continue;
}
AttachDevice(pDriverObject , pComDev , &fltDevObj, &realDevObj);
}
这里出现了两个自定函数 一个是 OpenCom 从这里看出 这个函数的参数是 一个用来计数的数值和一个状态值
返回的是一个驱动对象的指针
当这个指针为空的时候 则忽略后面的代码 直接执行下一次循环
现在我们看看OpenCom这个函数
PDEVICE_OBJECT OpenCom(ULONG id , NTSTATUS * status)
{
UNICODE_STRING ustrDevName ;
WCHAR wName[32];
PFILE_OBJECT pFileObj;
PDEVICE_OBJECT pDevObj;
memset(wName , 0 , sizeof(WCHAR)*32);
RtlStringCchPrintfW(wName , 32 , L"\\Device\\Serial%d" , id);
RtlInitUnicodeString(&ustrDevName , wName);
//打开设备
*status = IoGetDeviceObjectPointer(&ustrDevName , FILE_ALL_ACCESS ,&pFileObj , &pDevObj);
if (*status == STATUS_SUCCESS)
{
ObDereferenceObject(pFileObj); //解除文件对象
}
return pDevObj;
}
这里先初始化了一块内存 大小为32字节
然后使用RtlStringCchPrintfW初始化一个字符串 值是\\Device\\Serial跟输入的计数用的参数
然后使用IoGetDeviceObjectPointer来获取设备对象
因为IoGetDeviceObjectPointer会增加这个设备对象的引用计数 所以如果成功 就用ObDereferenceObject将引用计数减一
然后返回这个设备对象
然后是上面出现的第二个函数 也就是AttachDevice
NTSTATUS AttachDevice(
PDRIVER_OBJECT? ?pDriver ,
PDEVICE_OBJECT? ?pOldDeviceObj ,
PDEVICE_OBJECT * pNewDeviceObj ,
PDEVICE_OBJECT * pNext
)
{
NTSTATUS status;
PDEVICE_OBJECT pTopDev = NULL;
//生成设备
status = IoCreateDevice(
pDriver ,
0 ,
NULL ,
pOldDeviceObj->DeviceType ,
0 ,
FALSE ,
pNewDeviceObj
);
if (status != STATUS_SUCCESS)
{
return status;
}
这里他生成了一个虚拟设备
如果成功失败 则返回失败代码
if(pOldDeviceObj->Flags & DO_BUFFERED_IO)
{
(*pNewDeviceObj)->Flags |= DO_BUFFERED_IO;
}
if(pOldDeviceObj->Flags & DO_DIRECT_IO)
{
(*pNewDeviceObj)->Flags |= DO_DIRECT_IO;
}
if(pOldDeviceObj->Characteristics & FILE_DEVICE_SECURE_OPEN)#xa0; #xa0;
{
(*pNewDeviceObj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
}
(*pNewDeviceObj)->Flags |= DO_POWER_PAGABLE;
这一段代码 将新生成的设备 赋予了和即将被绑定的设备一样的属性
pTopDev = IoAttachDeviceToDeviceStack(*pNewDeviceObj , pOldDeviceObj);
if (pTopDev == NULL)//绑定失败
{
IoDeleteDevice(*pNewDeviceObj);
*pNewDeviceObj = NULL;
return (STATUS_UNSUCCESSFUL);
}
然后调用IoAttachDeviceToDeviceStack将生成的虚拟设备绑定到原驱动上
如果失败 则删除生成的设备 然后返回未成功
*pNext = pTopDev;
(*pNewDeviceObj)->Flags = (*pNewDeviceObj)->Flags & ~DO_DEVICE_INITIALIZING;//启动设备
return STATUS_SUCCESS;
}
如果绑定成功 则将传递进来的缓冲区的值设置成绑定成功的设备对象
然后将驱动状态清除DO_DEVICE_INITIALIZING以设置为启用
这里说一下
对于驱动程序在其 DriverEntry 例程中创建的任何设备对象,由 I/O 管理器负责清除 DO_DEVICE_INITIALIZING。对于除 DriverEntry 之外的任何例程中创建的任何设备对象,由驱动程序负责清除 DO_DEVICE_INITIALIZING
而DO_DEVICE_INITIALIZING 的目的是防止其它组件在驱动程序完成初始化设备对象之前向设备发送 I/O。
然后返回成功
这里说一下 之前代码中的常量
MAX_COM_NUM
至此绑定完成了
那么我们看看他是怎么处理动作的
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDeviceObject , IN PIRP Irp)
{
//KdPrint(("Enter HelloDDKDispatchRoutine!\n"));
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION Irpsp = IoGetCurrentIrpStackLocation(Irp);
ULONG i = 0 , j = 0, uLen = 0;
PUCHAR pBuf;
for (i = 0 ; i < MAX_COM_NUM ; i++)
{
if (fltDevObj == pDeviceObject)
{
//电源操作 直接放过
if (Irpsp->MajorFunction??== IRP_MJ_POWER)
{
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return PoCallDriver(realDevObj, Irp); //调用真实设备
这里?
大家记得之前的
AttachDevice(pDriverObject , pComDev , &fltDevObj, &realDevObj);
么?
这个函数将设备对象保存在fltDevObj这个数组中了哦
所以循环比对是否成功绑定过这个设备
如果绑定过 再执行内部的代码
那么 没绑定过呢?
没绑定过 就认为没这个设备 嘿嘿 就不管它了
因为32已经是个很大的数了不是么
保险起见我建议大家用128 虽然可能加载效率会降低些..但是没什么感觉的
函数中 发现 这个irp的操作是一个IRP_MJ_POWER 也就是电源操作的
调用PoStartNextPowerIrp来继续这个操作
然后IoSkipCurrentIrpStackLocation挂起这个irp
直接调用PoCallDriver把一个主操作为IRP_MJ_POWER的irp传递给之前保存的对应的真实设备去咯
if (Irpsp->MajorFunction == IRP_MJ_WRITE)
{
uLen = Irpsp->Parameters.Write.Length; //获取写入的长度
pBuf = NULL;
if (Irp->MdlAddress != NULL)
{
pBuf = (PUCHAR)MmGetSystemAddressForMdlSafe (Irp->MdlAddress,NormalPagePriority);
}
else{
pBuf = (PUCHAR)Irp->UserBuffer;
}
当IRP的主操作为写入 也就是IRP_MJ_WRITE时??将被解析
首先将写入的长度保存下来
然后初始化一个指针参数
只要写入的mdl地址不是空
那么就
MmGetSystemAddressForMdlSafe 的方式将这个mdl的物理地址获取出来
如果是空 那么就获取他的UserBuffer的地址
if(pBuf == NULL)
{
pBuf = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
}
但是用户地址还有可能是空 并且MmGetSystemAddressForMdlSafe也有可能失败
所以还需要校验一边
如果是空 则取得
Irp->AssociatedIrp.SystemBuffer
for (j = 0 ; j < uLen ; j++)
{
KdPrint(("comcap : SendData : %2x\r\n" , pBuf[j]));
}
然后循环输出写入的数据
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp , IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
然后 因为这里的过滤是截获到之后 不需要向下发送
所以直接返回已经处理完成
然后返回STATUS_SUCCESS
然后是卸载函数 这个函数一般不该提供 因为正常是重启卸载
但是作者这里提供了
这里我就不解释了 因为注释已经说的很清楚了