前几天,我看了网上流传的有关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!=0)//复制已经存在的处理函数指针,把他们复制进我们构建的一张新表中
{
CallBack=KernelCallbackTable;
u++;
}
CallBack=(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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)