NDIS and TDI Hooking, Part II

本文介绍了一种钩入NDIS和TDI层的方法,通过替换设备对象的主要函数来实现对网络数据包的拦截和键盘输入的监听。这种方法不仅适用于网络驱动,还可以应用于任何设备链。
By: andreas

This is the second and last article on how to hook into the NDIS and TDI
layer. The approach we will use will be slightly different from the NDIS
case. However, a neat side effect is that this method can be used to hook
into any device chain, for example the keyboard to sniff key strokes. It all boils down to getting a pointer to the device object and replace all major functions with our own dispatch function.

To be able to fully control the TDI layer, we need access to the IRP both
before and after the original driver has processed it. If we have that, we
can choose what the original driver should process and we can also alter
results before they are returned to user-space. The "before filtering" can
be accomplished in our own, new dispatch function and the "after filtering" can be accomplished in a completion routine.

First, to be able to overwrite and insert our own dispatch function, we need a pointer to the driver object we are going to hook into. An easy way to get this pointer is to call ObReferenceObjectByName with the appropriate driver name. Then we only have to save all old function pointers and overwrite the existing ones with our own. The code to do this would look something like the following:


DRIVER_OBJECT RealTDIDriverObject;

NTSTATUS HookTDI(void)
{
    NTSTATUS Status;
    UNICODE_STRING usDriverName;
    PDRIVER_OBJECT DriverObjectToHookPtr;
    UINT i;



    RtlInitUnicodeString(&usDriverName,L"//Driver//Tcpip");

    Status =
ObReferenceObjectByName(&usDriverName,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&DriverObjectToHookPtr);
    if(Status != STATUS_SUCCESS) return Status;

    for(i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++) {
        RealTDIDriverObject.MajorFunction[i] =
DriverObjectToHookPtr->MajorFunction[i];
        DriverObjectToHookPtr->MajorFunction[i] = TDIDeviceDispatch;
    }

    return STATUS_SUCCESS;
}




RealTDIDriverObject is a DRIVER_OBJECT where we save the original
information to both be able to call the old functions and also be able to
unhook once we are done. The orignal driver gets all its major functions
overwritten with a pointer to our own dispatch function, TDIDeviceDispatch.

We now have control over the IRPs before TDI can process them. But, we still have to make sure we can also control them once TDI is done with it but before it is returned to the IO handler and user-space. We will solve this in our dispatch function with the help of a completion routine. It is not as straight forward as it sounds, since we might be hooking the last entity in the chain, we can't just insert a completion routine with
IoSetCompletionRoutine (see the DDK docs), since it in that case never will be called. Completion routines are set in the next IRP stack location, not the current. If we are the last entity, there will be no next stack location in the IRP. Searching through the header files reveal IoSetCompletionRoutine as a macro which only gets the next IRP stack location and sets the CompletionRoutine pointer together with the Control element. Following the same principcal, we can set our own completion routine to regain control over the IRP with the following dispach function:


NTSTATUS TDIDeviceDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
    NTSTATUS Status;
    PIO_STACK_LOCATION StackLocationPtr;

    if(Irp == NULL) return STATUS_SUCCESS;

    StackLocationPtr = IoGetCurrentIrpStackLocation(Irp);
    if(StackLocationPtr->CompletionRoutine != NULL) StackLocationPtr->Context =
StackLocationPtr->CompletionRoutine;
    else StackLocationPtr->Context = NULL;
    StackLocationPtr->CompletionRoutine =
(PIO_COMPLETION_ROUTINE)TDICompletionRoutine;
    StackLocationPtr->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR |
SL_INVOKE_ON_CANCEL;

    Status =
RealTDIDriverObject.MajorFunction[StackLocationPtr->MajorFunction](DeviceObject,Irp);

    return Status;
}


What we actually do is faking a scenario where the layer above set the
completion routine for us. We also save a potentially already existing
completion routine in the Context element of the IRP. Control is set to
invoke the completion routine in all cases. There are 2 potential issues
with this code. First, we overwrite whatever is in the Context element.
Second, we never save the Control element, so we don't know when to invoke
an already existing completion routine. So far, I have not seen any
side-effects from doing this.

The completion routine would look something like:


NTSTATUS TDICompletionRoutine(PDEVICE_OBJECT DeviceObject,PIRP Irp,PVOID
Context)
{
    COMPLETIONROUTINE RealCompletionRoutine = (COMPLETIONROUTINE)Context;

    if(Context != NULL) return RealCompletionRoutine(DeviceObject,Irp,NULL);
    else return STATUS_SUCCESS;
}


It invokes a potential completion routine as soon as it is done and returns the status from it. Finally, unhooking the driver is just a question of restoring the pointers we overwrote in the hooking function:


NTSTATUS ReleaseTDIDevices(void)
{
    NTSTATUS Status;
    UNICODE_STRING usDriverName;
    PDRIVER_OBJECT DriverObjectToHookPtr;
    UINT i;

    RtlInitUnicodeString(&usDriverName,L"//Driver//Tcpip");

    Status =
ObReferenceObjectByName(&usDriverName,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&DriverObjectToHookPtr);
    if(Status != STATUS_SUCCESS) return Status;

    for(i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++)
        DriverObjectToHookPtr->MajorFunction[i] =
RealTDIDriverObject.MajorFunction[i];

    return STATUS_SUCCESS;
}



There is another way to accomplish the same result which utilizes a more
offically supported mode of operation. It is based upon attaching to the
device chain with GetDeviceObject and AttachToDevice, which will allow us to process all IRPs before the real device. Once in the dispatch function we contruct a new IRP and add a completion routine to regain control of the IRP before it is returned to the IO system and user-space.

One last important thing to mention; This code is quite untested. It seems
to work as intended but it has never been used in any major applications, so use it on your own risk. With that said, hope you have enjoyed this little article series.

This is the second and last article on how to hook into the NDIS and TDI
layer. The approach we will use will be slightly different from the NDIS
case. However, a neat side effect is that this method can be used to hook
into any device chain, for example the keyboard to sniff key strokes. It all boils down to getting a pointer to the device object and replace all major functions with our own dispatch function.

To be able to fully control the TDI layer, we need access to the IRP both
before and after the original driver has processed it. If we have that, we
can choose what the original driver should process and we can also alter
results before they are returned to user-space. The "before filtering" can
be accomplished in our own, new dispatch function and the "after filtering" can be accomplished in a completion routine.

First, to be able to overwrite and insert our own dispatch function, we need a pointer to the driver object we are going to hook into. An easy way to get this pointer is to call ObReferenceObjectByName with the appropriate driver name. Then we only have to save all old function pointers and overwrite the existing ones with our own. The code to do this would look something like the following:


DRIVER_OBJECT RealTDIDriverObject;

NTSTATUS HookTDI(void)
{
    NTSTATUS Status;
    UNICODE_STRING usDriverName;
    PDRIVER_OBJECT DriverObjectToHookPtr;
    UINT i;



    RtlInitUnicodeString(&usDriverName,L"//Driver//Tcpip");

    Status =
ObReferenceObjectByName(&usDriverName,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&DriverObjectToHookPtr);
    if(Status != STATUS_SUCCESS) return Status;

    for(i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++) {
        RealTDIDriverObject.MajorFunction[i] =
DriverObjectToHookPtr->MajorFunction[i];
        DriverObjectToHookPtr->MajorFunction[i] = TDIDeviceDispatch;
    }

    return STATUS_SUCCESS;
}




RealTDIDriverObject is a DRIVER_OBJECT where we save the original
information to both be able to call the old functions and also be able to
unhook once we are done. The orignal driver gets all its major functions
overwritten with a pointer to our own dispatch function, TDIDeviceDispatch.

We now have control over the IRPs before TDI can process them. But, we still have to make sure we can also control them once TDI is done with it but before it is returned to the IO handler and user-space. We will solve this in our dispatch function with the help of a completion routine. It is not as straight forward as it sounds, since we might be hooking the last entity in the chain, we can't just insert a completion routine with
IoSetCompletionRoutine (see the DDK docs), since it in that case never will be called. Completion routines are set in the next IRP stack location, not the current. If we are the last entity, there will be no next stack location in the IRP. Searching through the header files reveal IoSetCompletionRoutine as a macro which only gets the next IRP stack location and sets the CompletionRoutine pointer together with the Control element. Following the same principcal, we can set our own completion routine to regain control over the IRP with the following dispach function:


NTSTATUS TDIDeviceDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
    NTSTATUS Status;
    PIO_STACK_LOCATION StackLocationPtr;

    if(Irp == NULL) return STATUS_SUCCESS;

    StackLocationPtr = IoGetCurrentIrpStackLocation(Irp);
    if(StackLocationPtr->CompletionRoutine != NULL) StackLocationPtr->Context =
StackLocationPtr->CompletionRoutine;
    else StackLocationPtr->Context = NULL;
    StackLocationPtr->CompletionRoutine =
(PIO_COMPLETION_ROUTINE)TDICompletionRoutine;
    StackLocationPtr->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR |
SL_INVOKE_ON_CANCEL;

    Status =
RealTDIDriverObject.MajorFunction[StackLocationPtr->MajorFunction](DeviceObject,Irp);

    return Status;
}


What we actually do is faking a scenario where the layer above set the
completion routine for us. We also save a potentially already existing
completion routine in the Context element of the IRP. Control is set to
invoke the completion routine in all cases. There are 2 potential issues
with this code. First, we overwrite whatever is in the Context element.
Second, we never save the Control element, so we don't know when to invoke
an already existing completion routine. So far, I have not seen any
side-effects from doing this.

The completion routine would look something like:


NTSTATUS TDICompletionRoutine(PDEVICE_OBJECT DeviceObject,PIRP Irp,PVOID
Context)
{
    COMPLETIONROUTINE RealCompletionRoutine = (COMPLETIONROUTINE)Context;

    if(Context != NULL) return RealCompletionRoutine(DeviceObject,Irp,NULL);
    else return STATUS_SUCCESS;
}


It invokes a potential completion routine as soon as it is done and returns the status from it. Finally, unhooking the driver is just a question of restoring the pointers we overwrote in the hooking function:


NTSTATUS ReleaseTDIDevices(void)
{
    NTSTATUS Status;
    UNICODE_STRING usDriverName;
    PDRIVER_OBJECT DriverObjectToHookPtr;
    UINT i;

    RtlInitUnicodeString(&usDriverName,L"//Driver//Tcpip");

    Status =
ObReferenceObjectByName(&usDriverName,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&DriverObjectToHookPtr);
    if(Status != STATUS_SUCCESS) return Status;

    for(i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++)
        DriverObjectToHookPtr->MajorFunction[i] =
RealTDIDriverObject.MajorFunction[i];

    return STATUS_SUCCESS;
}



There is another way to accomplish the same result which utilizes a more
offically supported mode of operation. It is based upon attaching to the
device chain with GetDeviceObject and AttachToDevice, which will allow us to process all IRPs before the real device. Once in the dispatch function we contruct a new IRP and add a completion routine to regain control of the IRP before it is returned to the IO system and user-space.

One last important thing to mention; This code is quite untested. It seems
to work as intended but it has never been used in any major applications, so use it on your own risk. With that said, hope you have enjoyed this little article series.
内容概要:本文深入研究了基于最优滑模控制的永磁同步电机(PMSM)调速系统模型,重点利用Simulink工具搭建并仿真了该控制系统的动态响应特性。文章系统阐述了最优滑模控制策略的设计原理,突出其在削弱传统滑模控制固有抖振现象、增强系统鲁棒性方面的显著优势。通过与传统滑模控制方法的对比实验,充分验证了所提出方法在调速精度、抗外部干扰能力以及动态响应速度等方面的优越性能。研究内容涵盖PMSM数学建模、滑模面构造、最优控制律推导、Lyapunov稳定性分析、参数整定及Simulink仿真验证等完整环节,形成了一套严谨的控制算法设计与实现流程。; 适合人群:具备自动控制原理、现代控制理论基础和MATLAB/Simulink仿真操作能力,从事电机驱动控制、电力电子与电力传动、运动控制或自动化等相关领域研究的工程技术人员及高校研究生。; 使用场景及目标:① 深入掌握滑模控制理论及其在高性能电机调速系统中的具体应用方法;② 学习如何设计并实现能够有效抑制抖振的最优滑模控制器,以提升系统整体鲁棒性和控制品质;③ 利用Simulink平台独立完成从理论建模到仿真验证的全过程,服务于科研课题、课程设计或实际工程项目。; 阅读建议:建议读者务必结合MATLAB/Simulink环境动手复现文中模型,重点关注滑模切换面的设计准则、控制律的数学推导过程以及控制器参数的调节规律,并通过施加不同的负载扰动、设定多种转速指令等方式全面测试系统的动态与稳态性能,从而深刻理解最优滑模控制的核心机理与工程应用价值。
内容概要:本文提出了一种基于数据驱动的Koopman算子与递归神经网络(RNN)相结合的模型线性化方法,旨在解决纳米定位系统中因强非线性、迟滞和蠕变效应导致的建模困难问题。该方法通过Koopman算子将非线性动态系统映射至高维线性空间,利用RNN学习系统的时间序列演化特征,从而实现对复杂动态行为的精确建模与预测,并进一步集成于模型预测控制(MPC)框架中,显著提升了纳米定位系统的控制精度、动态响应能力与运行稳定性。整个算法体系在Matlab平台上完成代码实现与仿真实验验证,展示了良好的控制性能与工程应用潜力。; 适合人群:具备控制理论、非线性系统建模、机器学习及智能控制基础,从事精密仪器控制、高端制造装备研发、自动化系统设计等领域的研究生、科研人员及工程技术开发者。; 使用场景及目标:①应对扫描探针显微镜、光刻机、超精密加工平台等纳米级定位设备中的非线性建模挑战;②提升高精度运动系统的实时预测控制性能,抑制迟滞与蠕变带来的定位误差;③为数据驱动的非线性系统线性化与先进控制策略(如MPC)的融合提供可复现、可扩展的技术范例。; 阅读建议:建议读者结合提供的Matlab代码,深入理解Koopman观测矩阵构造、RNN网络训练流程及MPC控制器设计之间的协同机制,重点关注数据预处理、特征提取、模型训练与闭环控制仿真的完整链路,以便在相似高精度控制系统中进行迁移与优化应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值