您的位置:首页 > 编程语言

自己重新写的KeUserModeCallBack 例子,比网上的代码改进很多,请大家指教!

2011-08-14 00:19 369 查看
标 题: 【分享】【原创】自己重新写的KeUserModeCallBack 例子,比网上的代码改进很多,请大家指教!

作 者: czcqq

时 间: 2011-08-09,01:49:10

链 接: http://bbs.pediy.com/showthread.php?t=138461
前几天,我看了网上流传的有关KeUserModeCallBack 函数的使用方法(主要是:如果在回调你的应用程序的时候,应用程序发生了崩溃,或者在弹出那个消息框的时候,

你用任务管理器个KILL了的话,你的进程就会成为"僵尸",在系统中无法删除了),感觉有些不好,于是自己重新编写了代码,这个代码可以实现类似WINDOWS消息机制的一个机制,

来在进程间传递消息,如果在内核中使用的话,还可以从不同的内核线程中传递消息到你的应用程序(由于我编写的驱动是提供给应用程序调用的,所以我加上了有关内存读写的限制,

如果在驱动中使用的话,请去掉这些限制)

KeUserModeCallBack的调用过程,我相信大家已经知道了,

我就不重复了,我现在将代码贴出,并在每一句上给予讲解,讲的不好的请大家原谅,又不懂的,请大家在QQ上问我 :QQ 332096943

驱动程序编译环境 WDK 7600.16385.1

应用程序编译环境 VC6.0

驱动程序代码

源文件名 SendMessage.c

//////////////////////////////////////////////////////////////

程序定义

#include <ntifs.h>

#include "debug.h" //这个头文件中主要含有一个调试宏,用来测试是否是调试版本

#include "IoCreateDriver.h"

#include "EXTNDDRV.H"

#define DELAY_ONE_MICROSECOND (-10)

#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)

HANDLE ProcessHandle = NULL;

ULONG ApiIndex = 0;

LIST_ENTRY DataList;

LIST_ENTRY SendingDataList;

KEVENT ListEvent;

KSPIN_LOCK Lock;

typedef struct _SENDTOUSERMSG

{

ULONG MessageIndex;

size_t Size;

size_t DataSize;

char Dataof[1];

}SENDTOUSERMSG,*PSENDTOUSERMSG;//通过KeUserModeCallBack发送到你的应用程序处理函数的数据结构

typedef struct _MSGDATA

{

ULONG MessageIndex;

PVOID pData;

size_t DataSize;

NTSTATUS Status;

NTSTATUS *UserRetNtstatus;

int IsSend;

KEVENT Event;

LIST_ENTRY DataList;

}MSGDATA,*PMSGDATA;//用来将要发送的数据临时保存在链表中的一个数据结构

ULONG GetCurrentProcessPEB(VOID);//取得本进程的PEB结构

LARGE_INTEGER Time;

NTSTATUS SendData(PMSGDATA MsgData);//用于向你的处理函数发送数据的函数

NTSTATUS

DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath

);//入口函数

NTKERNELAPI

NTSTATUS

KeUserModeCallback(

IN ULONG ApiNumber,

IN PVOID InputBuffer,

IN ULONG InputLength,

OUT PVOID *OutputBuffer,

IN PULONG OutputLength

);

#if DBG

VOID

CallbackUnload(

IN PDRIVER_OBJECT DriverObject

);

#endif

NTSYSAPI

NTSTATUS

NTAPI SetCallBack(ULONG FunctionIndex); //用于设置索引,系统要根据这个索引,在KernelCallBackTable中取得指定函数的地址(在RING 3的代码中,我会贴出如何在本线程的KernelCallBackTable中添加自己的处理函数)

NTSYSAPI

NTSTATUS

NTAPI UnSetCallBack();//删除所有回调,并且让处于等待中的要处理的请求返回请求发起程序

NTSYSAPI

NTSTATUS

NTAPI CallBack();//用于激发KeUserNodeCallBack调用

VOID Process(

IN HANDLE ParentId,

IN HANDLE ProcessId,

IN BOOLEAN Create

);//用于收尾工作

UNICODE_STRING DeviceName;

UNICODE_STRING LinkName;

unsigned int MyStartingServiceId;

unsigned int StartingServiceId;

NTSTATUS

DriverDispatch(

IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp

);

NTSYSAPI

NTSTATUS

NTAPI UnSetCallBackA();//用于替代UnSetCallBack的一个函数

NTSYSAPI

NTSTATUS

NTAPI SendMessage(ULONG MessageIndex,PVOID Msg,size_t MsgSize,NTSTATUS *UserRetStatus);//消息发送函数,由消息发送方发起

#if DBG

PVOID ServiceTableBase[]={(PVOID)CallBack,(PVOID)UnSetCallBack,(PVOID)SetCallBack,(PVOID)SendMessage};//我们自己构建的一张SSDT表

#else

PVOID ServiceTableBase[]={(PVOID)CallBack,(PVOID)UnSetCallBackA,(PVOID)SetCallBack,(PVOID)SendMessage};

#endif

unsigned char ParamTableBase[]={0,0,4,sizeof(ULONG)+sizeof(PVOID)+sizeof(size_t)+sizeof(NTSTATUS *)};//用于描述我们自己SSDT表的一个参数表(SSPT表)

__declspec(dllimport)PMDL NTAPI IoCreateWriteMdlForAddress(PVOID InAddress,PVOID *OutAddress,size_t Size);//自己封装的一个函数,用于生成一个MDL

__declspec(dllimport)VOID NTAPI IoFreeMdlForAddress(PVOID Address,PMDL pMdl);//自己封装的一个函数,用于销毁一个MDL

__declspec(dllimport)NTSTATUS _stdcall AddServices(PVOID *ServiceTableBase,unsigned char *ParamTableBase,unsigned int *MyStartingServiceId,unsigned int NumberOfServices);

//自己封装的一个函数,用来向系统的SSDT表,添加新的函数,参数MyStartingServiceId返回的是新函数在SSDT中的索引,也就是我们自己构建的SSDT表中第一个函数的索引)

NTSYSAPI

NTSTATUS

NTAPI

ZwQueryInformationProcess (

IN HANDLE ProcessHandle,

IN PROCESSINFOCLASS ProcessInformationClass,

OUT PVOID ProcessInformation,

IN ULONG ProcessInformationLength,

OUT PULONG ReturnLength OPTIONAL

);

NTSTATUS

NTAPI UnSetCallBackA()

{

return 0;

}

NTSTATUS

DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath

)

{

PDEVICE_OBJECT DeviceObject;

PVOID DeviceExtensionAddress;

NTSTATUS Status = 0;

Time.QuadPart = (100*DELAY_ONE_MICROSECOND);

KeInitializeEvent(&ListEvent,SynchronizationEvent,0);

DriversUnload(DriverObject,CallbackUnload);

InitializeListHead(&DataList);

InitializeListHead(&SendingDataList);

KeInitializeSpinLock(&Lock);

DEBUG;//这个代码在调试版本中为 _asm int 3 因此如果你编译的是调试版本的话,请不要在没有打开任何内核调试器,或者系统调试模式的状态下使用,否则会直接BOSD,如果编译的是发行版本

//则没有这个限制,因为在发行版本中 DEBUG 为空

RtlInitUnicodeString(&DeviceName,L"\\Device\\GetInt");

RtlInitUnicodeString(&LinkName,L"\\DosDevices\\GetInt");

DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatch;

DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverDispatch;

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;

Status=MyIoCreateDevice(DriverObject,0,&DeviceExtensionAddress,&DeviceName,&LinkName,FILE_DEVICE_UNKNOWN,

0,FALSE,&DeviceObject,NULL);

if (NT_SUCCESS(Status))

{

Status = AddServices(ServiceTableBase,ParamTableBase,&MyStartingServiceId,4);//向SSDT添加服务函数

StartingServiceId=MyStartingServiceId;

}

if (NT_SUCCESS(Status))

{

SetFlag(DeviceObject->Flags, DO_BUFFERED_IO);

PsSetCreateProcessNotifyRoutine(Process,FALSE);

}else

{

IoDeleteDevice(DeviceObject);

IoDeleteSymbolicLink(&LinkName);

}

return Status;

}

#if DBG

VOID

CallbackUnload(

IN PDRIVER_OBJECT DriverObject

)

{

}

#endif

NTSTATUS

NTAPI SetCallBack(ULONG FunctionIndex)//主要保存应用发来的处理函数在KernelCallBackTable中的索引,这个函数必须由处理函数所在的线程调用

{

ULONG KernelCallBackTable;

ULONG PebAddr;

_try

{

PebAddr=GetCurrentProcessPEB();

if(PebAddr==NULL)

return STATUS_ACCESS_DENIED;

KernelCallBackTable=*(ULONG*)(PebAddr+0x2C);

if(KernelCallBackTable==0)

return STATUS_ACCESS_DENIED;

}

_except(EXCEPTION_EXECUTE_HANDLER)

{

return GetExceptionCode();

}

if(FunctionIndex>500)//我这里设定,索引大于500时返回一个错误

return STATUS_ACCESS_DENIED;

if(FunctionIndex<1)

return STATUS_ACCESS_DENIED;

if((ProcessHandle!=NULL)|(ApiIndex!=NULL))

return STATUS_ACCESS_DENIED;

ProcessHandle=PsGetCurrentProcessId();//记录下调用进程的PID

ApiIndex=FunctionIndex;//记录下索引

return 0;

}

NTSTATUS

NTAPI UnSetCallBack()

{

//用于处理在处理进程退出以后,还没有来得及处理,和正在处理,并且没有处理完成的请求的处理

PMSGDATA MsgData;

PLIST_ENTRY pList = NULL;

pList=ExInterlockedRemoveHeadList(&DataList,&Lock);//从等待处理的请求的链表中取出请求,如果链表为空,就停止取出等待处理的请求

while(pList!=NULL)

{

MsgData=CONTAINING_RECORD(pList,MSGDATA,DataList);

MsgData->Status = STATUS_ACCESS_DENIED;

KeSetEvent(&MsgData->Event,IO_NO_INCREMENT,FALSE);

KeDelayExecutionThread(KernelMode,FALSE,&Time);

KeClearEvent(&MsgData->Event);

ExFreePool(MsgData);

pList=ExInterlockedRemoveHeadList(&DataList,&Lock);

}

pList=ExInterlockedRemoveHeadList(&SendingDataList,&Lock);//从正在处理的请求的链表中取出请求,如果链表为空,就停止取出正在处理的请求

while(pList!=NULL)

{

MsgData=CONTAINING_RECORD(pList,MSGDATA,DataList);

MsgData->Status = STATUS_ACCESS_DENIED;

KeSetEvent(&MsgData->Event,IO_NO_INCREMENT,FALSE);

KeDelayExecutionThread(KernelMode,FALSE,&Time);

KeClearEvent(&MsgData->Event);

ExFreePool(MsgData);

pList=ExInterlockedRemoveHeadList(&SendingDataList,&Lock);

}

ApiIndex=NULL;

ProcessHandle=NULL;

return 0;

}

NTSTATUS

NTAPI CallBack()//这个函数用于激发KeUserModeCallBack的调用,这个函数必须在处理函数所在线程中调用

{

ULONG KernelCallBackTable;

NTSTATUS *pStatus=NULL;

PMSGDATA MsgData;

PLIST_ENTRY pList = NULL;

ULONG PebAddr;

_try

{

PebAddr=GetCurrentProcessPEB();//取得PEB结构

if(PebAddr==NULL)

return STATUS_ACCESS_DENIED;

KernelCallBackTable=*(ULONG*)(PebAddr+0x2C);//从PEB中取得KernelCallBackTable结构

if(KernelCallBackTable==0)

return STATUS_ACCESS_DENIED;

}

_except(EXCEPTION_EXECUTE_HANDLER)

{

return GetExceptionCode();

}

if((ProcessHandle==NULL)|(ApiIndex==NULL))//检查是否有处理函数的索引

return STATUS_ACCESS_DENIED;

if(ProcessHandle!=PsGetCurrentProcessId())

return STATUS_ACCESS_DENIED;

pList=ExInterlockedRemoveHeadList(&DataList,&Lock);//取出需要处理的请求

if(pList==NULL)

return -1;//如果没有请求,就返回

MsgData=CONTAINING_RECORD(pList,MSGDATA,DataList);//取出待发送的请求

MsgData->IsSend=1;//设置标志为正在处理

ExInterlockedInsertTailList(&SendingDataList,&MsgData->DataList,&Lock);//将请求插入正在处理链表

pStatus=MsgData->UserRetNtstatus;//指向一个接收函数状态的缓冲区

MsgData->Status = SendData(MsgData);//调用KeUserModeCallBack回调处理进程中的处理函数

*pStatus=MsgData->Status;//接收用户返回的状态

ExInterlockedRemoveHeadList(&SendingDataList,&Lock);//将请求从正在处理链表中删除

MsgData->IsSend=0;//恢复标志

KeSetEvent(&MsgData->Event,IO_NO_INCREMENT,FALSE);//通知请求发起程序,请求的处理已经完成

KeDelayExecutionThread(KernelMode,FALSE,&Time);//程序暂停100纳秒

KeClearEvent(&MsgData->Event);//清除事件,并且回收资源

ExFreePool(MsgData);

return 0;

}

NTSTATUS

NTAPI SendMessage(ULONG MessageIndex,PVOID Msg,size_t MsgSize,NTSTATUS *UserRetStatus)

{

PVOID pDataAddress = NULL;

NTSTATUS UserStatus = -1;

PMDL pMdl = NULL;

PMSGDATA MsgData=NULL;

KEVENT Event;

NTSTATUS Status = 0;

if(MsgSize==0)

return STATUS_INVALID_PARAMETER;

if((ProcessHandle==NULL)|(ApiIndex==NULL))

return STATUS_ACCESS_DENIED;

_try

{

ProbeForWrite(UserRetStatus,sizeof(NTSTATUS),sizeof(NTSTATUS));//测试返回缓冲区是否可写

}

_except(EXCEPTION_EXECUTE_HANDLER)

{

Status = GetExceptionCode();

return STATUS_INVALID_PARAMETER;

}

_try

{

ProbeForRead(Msg,MsgSize,sizeof(ULONG));//测试需要发送的数据缓冲区是否可读

}

_except(EXCEPTION_EXECUTE_HANDLER)

{

Status = GetExceptionCode();

return STATUS_INVALID_PARAMETER;

}

*UserRetStatus=(NTSTATUS)-1;

MsgData=(PMSGDATA)ExAllocatePool(NonPagedPool,sizeof(MSGDATA));分配用于记录要发送的请求的数据结构

if(MsgData==NULL)

return STATUS_INSUFFICIENT_RESOURCES;

pMdl=IoCreateWriteMdlForAddress(Msg,&pDataAddress,MsgSize);//为请求建立一个MDL,这个MDL 地址指向了请求缓冲区,并且这个缓冲区已经可以用在内核中了

if(pMdl==NULL)//MDL建立失败,这个时候必须返回失败

{

ExFreePool(MsgData);

return STATUS_INSUFFICIENT_RESOURCES;

}

MsgData->pData=pDataAddress;

MsgData->DataSize=MsgSize;

MsgData->MessageIndex=MessageIndex;

KeInitializeEvent(&MsgData->Event,SynchronizationEvent,0);//初始化一个事件

MsgData->UserRetNtstatus=&UserStatus;

MsgData->IsSend = 0;

ExInterlockedInsertTailList(&DataList,&MsgData->DataList,&Lock);//将请求插入等待处理的请求的链表中

KeWaitForSingleObject(&MsgData->Event,Executive,KernelMode,0,NULL);//等待请求的处理

IoFreeMdlForAddress(pDataAddress,pMdl);//回收资源

*UserRetStatus=UserStatus;

return 0;

}

VOID Process(

IN HANDLE ParentId,

IN HANDLE ProcessId,

IN BOOLEAN Create

)

{

if(Create==FALSE)

if(ProcessHandle==ProcessId)

UnSetCallBack();//在处理进程退出前没有处理的请求,在这里给予处理

}

ULONG GetCurrentProcessPEB(VOID)

{

PROCESS_BASIC_INFORMATION BasicInfo={0};

NTSTATUS status;

ULONG ReturenLength;

status=ZwQueryInformationProcess(NtCurrentProcess(),

ProcessBasicInformation,

&BasicInfo,

sizeof(PROCESS_BASIC_INFORMATION),

&ReturenLength);

if (NT_SUCCESS(status))

{

return (ULONG)BasicInfo.PebBaseAddress;

}

return 0;

}

NTSTATUS SendData(PMSGDATA MsgData)//获取请求,并且交予请求处理函数给予处理

{

ULONG ResultLentgh;

PVOID ResultBuffer;

PSENDTOUSERMSG SendToUserData = NULL;//指向用户缓冲区的数据结构指针

PVOID pBuf = NULL;

NTSTATUS Status = 0;

ULONG KernelCallBackTable;

ULONG PebAddr;

size_t DataSize= MsgData->DataSize+sizeof(SENDTOUSERMSG)+1;//计算要发送到处理函数的数据大小

PebAddr=GetCurrentProcessPEB();

__try

{

KernelCallBackTable=*(ULONG*)(PebAddr+0x2C);//根据PEB取得KernelCallBackTable的地址,如果KernelCallBackTable为空,那么我们回调过去的话,处理进程肯定崩溃,所以必须检查是否为0

if(KernelCallBackTable==0)

return 0;

Status=ZwAllocateVirtualMemory(NtCurrentProcess(),&pBuf,0,&DataSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);//由于应用程序无法直接处理驱动的数据,所以我们需要应用程序的缓冲区来接收数据

//

if (!NT_SUCCESS(Status))

return Status;

if(DataSize<MsgData->DataSize+sizeof(SENDTOUSERMSG)+1)//检查申请到的缓冲区大小,如果小于要发送的数据,就直接返回资源不足

{

ZwFreeVirtualMemory(NtCurrentProcess(),&pBuf,&DataSize,MEM_RELEASE);

return STATUS_INSUFFICIENT_RESOURCES;

}

RtlZeroMemory(pBuf,DataSize);//清空缓冲区

SendToUserData=(PSENDTOUSERMSG)pBuf;//填写缓冲区指针

RtlCopyMemory(&SendToUserData->Dataof,MsgData->pData,MsgData->DataSize);//填充要发送的请求,请求保存在MsgData->pData的一个MDL映射到内存的一个缓冲区中,并且在内核是可以读取的

SendToUserData->MessageIndex=MsgData->MessageIndex;//填写我们自定义的请求号,用于我们自己函数的请求的区分

SendToUserData->DataSize=MsgData->DataSize;//填写请求的大小

SendToUserData->Size=sizeof(SENDTOUSERMSG)+MsgData->DataSize;//填写实际发送到处理函数的数据大小

Status=KeUserModeCallback(ApiIndex,pBuf,DataSize,&ResultBuffer,&ResultLentgh);//回调我们的处理函数,ApiIndex是我在处理进程中预先设定好的,他的值由SetCallBack函数传入

//然后系统会根据ApiIndex,在KernelCallBackTable中找到并且回调我们的处理函数

ZwFreeVirtualMemory(NtCurrentProcess(),&pBuf,&DataSize,MEM_RELEASE);

}

__except(1)

{

DbgPrint("Unknown Error occured.\n");

return GetExceptionCode();

}

return Status;

}

NTSTATUS

DriverDispatch(

IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp

)

{

PIO_STACK_LOCATION irpStack;

PVOID ioBuffer;

ULONG inputBufferLength;

ULONG outputBufferLength;

NTSTATUS ntStatus;

Irp->IoStatus.Status = STATUS_SUCCESS;

Irp->IoStatus.Information = 0;

irpStack = IoGetCurrentIrpStackLocation (Irp);

switch (irpStack->MajorFunction)

{

case IRP_MJ_DEVICE_CONTROL:

trace(("EXTNDDRV.SYS: IRP_MJ_CLOSE\n"));

switch (irpStack->Parameters.DeviceIoControl.IoControlCode)

{

case IOCTL_EXTNDDRV_GET_STARTING_SERVICEID:

trace(("EXTNDDRV.SYS: IOCTL_EXTNDDRV_GET_STARTING_SERVICEID\n"));

outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

if (outputBufferLength<sizeof(StartingServiceId)) {

Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;

} else {

ioBuffer = (PULONG)Irp->AssociatedIrp.SystemBuffer;

memcpy(ioBuffer, &StartingServiceId, sizeof(StartingServiceId));

Irp->IoStatus.Information = sizeof(StartingServiceId);

}

break;

}

break;

}

ntStatus = Irp->IoStatus.Status;

IoCompleteRequest (Irp,

IO_NO_INCREMENT

);

return ntStatus;

}

/////////////////////////////////////////////////////////////////////////////////////////

应用程序代码

我主要讲解,如何在KernelCallBackTable中添加我们自定义的函数,其余部分请看我发上来的代码

int main(int argc, char* argv[])

{

ULONG *KernelCallbackTable = 0;

if(!SetStartingServiceId())

return 0;

::LoadLibrary("User32.dll");

_asm mov eax,fs:[18h] //首先,我们通过fs寄存器来取得我们的TEB

_asm mov eax,dword ptr ds:[eax+30h] //TEB偏移0x30处即PEB,放到eax中

_asm mov eax,dword ptr ds:[eax+2Ch]//TEB偏移0x2Ch处即KernelCallbackTable,放到eax中

_asm mov KernelCallbackTable,eax

ULONG u = 0;

if(KernelCallbackTable!=NULL)

while(KernelCallbackTable[u]!=0)//复制已经存在的处理函数指针,把他们复制进我们构建的一张新表中

{

CallBack[u]=KernelCallbackTable[u];

u++;

}

CallBack[u]=(ULONG)GetMsg;//在我们的新表中添加我们自己的处理函数的指针

_asm mov eax,fs:[18h]//我们通过fs寄存器来取得我们的TEB

_asm mov eax,dword ptr ds:[eax+30h]//TEB偏移0x30处即PEB,放到eax中

_asm lea ecx,CallBack//取得我们新表的地址

_asm mov [eax+2Ch],ecx//替换系统中原来的KernelCallbackTable的地址,由于原KernelCallbackTable表中的函数地址已经被复制到我们的新表中了,所以不怕来自GUI的调用

if(SetCallBack(u)==0)//这个时候的u 就是KeUserModeCallBack 回调时需要的索引号,它被记录在驱动的ApiIndex变量中

{

printf("设置处理函数成功!正在等待处理请求!\n");

}

NTSTATUS Status = Rin0CallBack();

while(Exit==0)

{

_try

{

Sleep(10);

Status = Rin0CallBack();

}

_except(EXCEPTION_EXECUTE_HANDLER)

{

_asm mov eax,0

_asm int 2bh

::ExitProcess(GetExceptionCode());

return GetExceptionCode();

}

}

printf("程序退出!清空所有处理请求!\n");

return 0;

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

由于时间的关系,我只贴到这里,其余的请看我的代码 另外提醒一下,DEBUG; 这个代码在调试版本中为 _asm int 3 因此如果你编译的是调试版本的话,请不要在没有打开任何内核调试器,或者系统调试模式的状态下使用,否则会直接BOSD,如果编译的是发行版本

//则没有这个限制,因为在发行版本中 DEBUG 为空,请大家注意。

我的代码在WIN XP SP2中编译,并且运行成功 如果程序又什么错误,或者是稳定性上的问题的话,请同学们给予指正 QQ 332096943

代码地址:http://bbs.pediy.com/showthread.php?t=138461
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐