Introduction
This article teaches you the way to detect Process/Thread creation/destruction on Win9x platforms.
Background Of Project
On Windows, processes and threads are the main area of interest for a system programmer. Many of you might have seen many tools & code for dealing with processes and threads on Windows NT/2000 based systems. Here I am introducing a process/thread creation/destruction detector for Windows 95/98 , really...:)
Description of Technique
Till now, you might have found ways to detect process creation on NT platforms using Kernel mode component which uses PsSetCreateProcessNotifyRoutine
. But like me, you might not have found a way to detect process/threads on Win9X platforms. I found many places on the Web, describing about process creation on Win9X using CREATE_PROCESS
message of VMM, but never found it easy to add handler to CREATE_PROCESS
in VXD. Because it's undocumented in 98 DDK. MS 98DDK headers/documentation have description about CREATE_THREAD
message, but not for CREATE_PROCESS
. You will find the definition of CREATE_PROCESS
, but wont find how to get the PID of a newly created process. For thread (CREATE_THREAD
), the thread ID is passed in EDI register, but for CREATE_PROCESS
it's not there. At last, by trial and error I found where it is, the PID value, it is in EDX reg.
Well, let's start talking about the project. There are 2 components in this project: 1st is Win32 RING 3 executable, which creates handle to VXD and passes function address to get called when process/thread creates/destroys. Second is VXD (Not WDM) which uses CREATE_PROCESS
, CREATE_THREAD
, DESTROY_THREAD
, DESTROY_PROCESS
VMM messages to detect the process/thread execution/death. VXD uses APC mechanism to call user mode program's function.
Background about VXD component: On Win9x/ME platforms (Consumer Windows), VXDs are the kernel mode components. Simply saying, VXD is ring 0 component on Win9x. VXDs are different from NT drivers in many ways. Like VXDs are not PE binaries, they have VMM's message processing kind of architecture which is very similar to Win32 message processing. VMM (Win98 kernel) sends many messages to VXD. Dispatch Handler defined in VXD gets called for these messages. The parameters like PID/TID are passed in CPU registers. The VXD presented here processes CREATE_PROCESS
, DESTROY_PROCESS
, CREATE_THREAD
, DESTROY_THREAD
messages, and calls ring 3 function (from kernel mode to user mode, great !) using APC. Following is the messages map of VXD, to map functions to messages. You can understand it by looking at it, similar to MFC message maps.
BEGIN_DISPATCH_MAP
ON_SYS_DYNAMIC_DEVICE_INIT(OnSysDynamicDeviceInit)
ON_SYS_DYNAMIC_DEVICE_EXIT(OnSysDynamicDeviceExit)
ON_W32_DEVICEIOCONTROL(OnW32DeviceIoControl)
ON_CREATE_PROCESS(OnProcessCreate)
ON_CREATE_THREAD(OnCreateThread)
ON_DESTROY_THREAD(OnDestroyThread)
ON_DESTROY_PROCESS(OnDestroyProcess)
END_DISPATCH_MAP
Well, following is the DeviceIOControl
handler in VXD. This function is called in VXD whenever DeviceIoControl
, CreateFile
and CloseFile
are called in user mode on VXD's handle.
DWORD OnW32DeviceIoControl(ULONG dwService, DWORD dwDDB,
DWORD hDevice, struct DIOCParams* lpDIOCParms
)
{
DWORD dwRet = 0;
switch (dwService)
{
case DIOC_OPEN:
_Debug_Printf_Service("Created Handle to Chikago driver!\n");
break;
case DIOC_CLOSEHANDLE:
_Debug_Printf_Service("Closed Handle to Chikago driver!\n");
dwRet = 0;
Return success
reak;
case IOCTL_TEST:
if(lpDIOCParms->lpcbBytesReturned)
*((PDWORD)(lpDIOCParms->lpcbBytesReturned)) = 0;
FunctionEventAPC=*((PVOID*)lpDIOCParms->lpvInBuffer );
TheThreadT=Get_Cur_Thread_Handle();
dwRet = 0;
break;
case IOCTL_RELEASE:
_Debug_Printf_Service("Release Memory Called%d\n",0);
_HeapFree(*(PVOID*)lpDIOCParms->lpvInBuffer,0);
default:
dwRet = 1;
break;
}
return dwRet;
}
Above shown function is the ENGINE part of VXD, it processes IOCTL passed from user mode application. Here you can see 2 major IOCTL codes: IOCTL_TEST
and IOCTL_RELEASE
. IOCTL_TEST
is used to pass the address of callback function using DeviceIoControl
. See following code of ring 3 EXE which calls DeviceIOControl
to pass the function address. (The function names APCFunc
will get called on process/thread creation/death.)
int main()
{
BOOL bRet;
DWORD cbRet;
char buf[80];
PVOID inbuf;
inbuf=APCFunc;
SetCurrentDirectory("..\\\\BIN");
hDevice = CreateFile("\\\\.\\CHIKAGO.VXD",
0,
0,
NULL,
0,
FILE_FLAG_DELETE_ON_CLOSE,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
printf("Failed to open handle to CHIKAGO.VXD\n");
else
{
bRet = DeviceIoControl(hDevice,
(DWORD)IOCTL_TEST,
&inbuf,
sizeof(PVOID),
buf,
sizeof(buf),
&cbRet,
NULL);
if (bRet)
printf("Press CTRL+C to terminate");
else
printf("IOCTL_TEST failed\n");
while(1)
SleepEx(-1,TRUE);
CloseHandle(hDevice);
}
return 0;
}
When VXD gets call for on IOCTL_TEST
, it saves the input buffer in global variable named PVOID
FunctionEventAPC
. The calling thread ID is also stored in TheThreadT
named DWORD
variable since it's necessary to use APC. Now following is CREATE_PROCESS
handler routine (other handlers are same as this.):
void OnProcessCreate(DWORD pid)
{
APCDataX * apm;
apm=(APCDataX*)_HeapAllocate(sizeof(APCDataX),0);
apm->dwCreated =TRUE;
apm->dwIdDATA =pid;
apm->dwThreadOrProcess =TRUE;
_Debug_Printf_Service("%d Process Created\n",pid);
_VWIN32_QueueUserApc(FunctionEventAPC,(DWORD)apm,TheThreadT);
}
void OnCreateThread(DWORD tid)
{
APCDataX * apm;
apm=(APCDataX*)_HeapAllocate(sizeof(APCDataX),0);
apm->dwCreated =TRUE;
apm->dwIdDATA =tid;
apm->dwThreadOrProcess =FALSE;
_Debug_Printf_Service("%d Thread Created\n",tid);
_VWIN32_QueueUserApc(FunctionEventAPC,(DWORD)apm,TheThreadT);
}
void OnDestroyProcess(DWORD pid)
{
APCDataX * apm;
apm=(APCDataX*)_HeapAllocate(sizeof(APCDataX),0);
apm->dwCreated =FALSE;
apm->dwIdDATA =pid;
apm->dwThreadOrProcess =TRUE;
_Debug_Printf_Service("%d Process Destroyed\n",pid);
_VWIN32_QueueUserApc(FunctionEventAPC,(DWORD)apm,TheThreadT);
}
void OnDestroyThread(DWORD tid)
{
APCDataX * apm;
apm=(APCDataX*)_HeapAllocate(sizeof(APCDataX),0);
apm->dwCreated =FALSE;
apm->dwIdDATA =tid;
apm->dwThreadOrProcess =FALSE;
_Debug_Printf_Service("%d Thread Destroyed\n",tid);
_VWIN32_QueueUserApc(FunctionEventAPC,(DWORD)apm,TheThreadT);
}
This handler queues an APC to the thread (which is main function of ring 3 exe in our case.). The VXD passes a structure pointer filled with PID and flags to differentiate between process and thread to user mode function (APCFunc
). Here is APCFunc
:
void APCFunc(struct APCDataXX * data)
{
if(data->dwThreadOrProcess==TRUE)
{
if(data->dwCreated==TRUE)
{
printf("Process Created %u\n",data->dwIdDATA);
}
else
{
printf("Process Destroyed %u\n",data->dwIdDATA);
}
}
else
{
if(data->dwCreated ==TRUE)
{
printf("Thread Created %u\n",data->dwIdDATA);
}
else
{
printf("Thread Destroyed %u\n",data->dwIdDATA);
}
}
DeviceIoControl(hDevice, IOCTL_RELEASE, &data, sizeof(PVOID),0,0,0,0);
}
Quite simple! It identifies whether process or thread was created/died. And then sends IOCTL_RELESE
to free the memory allocated by VXD. VXD frees the memory using _HeapFree
service/function of VMM.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.