Windows编程之双进程守护
需求分析
设计实现双进程守护程序,当两个进程同时运行时,关闭其中任意一个,另一个进程会识别出来并马上打开被关闭的进程。
设计原理
相关函数
CreateEvent( )
CreateEvent是一个Windows API函数。它用来创建或打开一个命名的或无名的事件对象。如果想为对象指定一个访问掩码,应当使用CreateEventEx函数。
语法:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性
BOOL bManualReset,// 复位方式
BOOL bInitialState,// 初始状态
LPCTSTR lpName // 对象名称
);
参数:
lpEventAttributes[输入]
一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
bManualReset[输入]
指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState[输入]
指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName[输入]
指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
如果lpName为NULL,将创建一个无名的事件对象。
如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。
终端服务(Terminal Services):名称中可以加入”Global\”或是”Local\”的前缀,这样可以明确的将对象创建在全局的或事务的命名空间。名称的其它部分除了反斜杠( \ ),可以使用任意字符。详细内容可参考Kernel Object Name Spaces。
返回值:
如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。
如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。
OpenEvent():
OpenEvent是一个函数,可以用来执行返回事件对象的句柄。
函数功能:
打开一个已经存在的命名事件对象
函数原型
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
参数说明
dwDesiredAccess
指定对事件对象的请求访问权限,如果安全描述符指定的对象不允许要求通过对调用该函数的过程,函数将返回失败。
该参数必须设置为以下值:
EVENT_ALL_ACCESS
指定事件对象所有可能的权限
bInheritHandle
指定是否返回的句柄是否继承 。该参数必须设置为false
lpName
指向一个以null结束的字符串,即将要打开的事件对象的名字。名称是区分大小写的。
返回值
函数执行成功则返回事件对象的句柄;失败则返回NULL,获取错误信息可以使用GetLastError
WaitForSingleObject( )
WaitForSingleObject是一种Windows API函数,当等待仍在挂起状态时,句柄被关闭,那么函数行为是未定义的。该句柄必须具有 SYNCHRONIZE 访问权限。
WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);
hHandle
对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。
dwMilliseconds
定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。
CreateMutex()
CreateMutex是一个计算机函数,作用是找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
BOOL bInitialOwner, // 初始化互斥对象的所有者
LPCTSTR lpName // 指向互斥对象名的指针
);
返回值:
Long,如执行成功,就返回互斥体对象的句柄;零表示出错。会设置GetLastError。如果返回的是一个有效句柄,但指定的名字已经存在,GetLastError也会设为ERROR_ALREADY_EXISTS,bInitialOwner的值将会被忽略。如果调用者限制了权限,GetLastError将会返回ERROR_ACCESS_DENIED,这个时候应该使用OpenMutex函数。
参数:
lpMutexAttributes SECURITY_ATTRIBUTES
指定一个SECURITY_ATTRIBUTES结构,或传递零值(将参数声明为ByVal As Long,并传递零值),表示使用不允许继承的默认描述符
bInitialOwner BOOL,如创建进程希望立即拥有互斥体,则设为TRUE。一个互斥体同时只能由一个线程拥有
lpName String
指定互斥体对象的名字。用vbNullString创建一个未命名的互斥体对象。如已经存在拥有这个名字的一个事件,则打开现有的已命名互斥体。这个名字可能不与现有的事件、信号机、可等待计时器或文件映射相符
OpenMutex()
OpenMutex函数为现有的一个已命名互斥体对象创建一个新句柄。
HANDLE OpenMutex(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name
);
参数:
dwDesiredAccess:
MUTEX_ALL_ACCESS 请求对互斥体的完全访问
MUTEX_MODIFY_STATE 允许使用 ReleaseMutex 函数
SYNCHRONIZE 允许互斥体对象同步使用
bInheritHandle :
如希望子进程能够继承句柄,则为TRUE
lpName :
要打开对象的名字
返回值:
如执行成功,返回对象的句柄;零表示失败。
设计详细
采用两种方法实现双进程守护。
方法1.
创建两个event句柄,使用CreateEvent创建打开事件对象。进入循环监控,如果进程2没有被打开,则使用CreateProcess函数打开进程,并显示其PID,使用WaitForSingleObject函数等待守护的进程被关闭,如果守护的进程被关闭,则Sleep 0.5秒后进入下一次循环,打开守护的进程。
进程2也使用于进程1同样的原理,两个进程相互守护。(注,编译链接成功后,把工程的Debug文件夹中的exe程序取出,两者分别命名为protect1、protect2,两者需要放在同一个目录下运行)。
代码:
守护进程1
#include <windows.h>
#include <stdio.h>
int main()
{
HANDLE event1;
HANDLE event2;
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
event1=CreateEvent(NULL, FALSE, TRUE, "Local\\p1");
while (true)
{
if (!(event2 = OpenEvent(EVENT_MODIFY_STATE, FALSE, "Local\\p2")))
{
if (!CreateProcess("protect2.exe",
NULL,
NULL,
NULL,
NULL,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
))
{
printf("打开守护进程失败!\n");
}
else printf("打开守护进程成功!PID:%d \n", pi.dwProcessId);
WaitForSingleObject(pi.hProcess, INFINITE);
printf("守护进程被关闭!\n");
CloseHandle(pi.hProcess);
}
CloseHandle(event2);
Sleep(1000);
}
return 0;
}
守护进程2:
#include <windows.h>
#include <stdio.h>
int main()
{
HANDLE event1;
HANDLE event2;
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
event2 = CreateEvent(NULL, FALSE, TRUE, "Local\\p2");
while (true)
{
if (!(event1 = OpenEvent(EVENT_MODIFY_STATE, FALSE, "Local\\p1")))
{
if (!CreateProcess("protect1.exe",
NULL,
NULL,
NULL,
NULL,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
))
{
printf("打开守护进程失败!\n");
}
printf("打开守护进程成功!PID:%d \n", pi.dwProcessId);
WaitForSingleObject(pi.hProcess, INFINITE);
printf("守护进程被关闭!\n");
CloseHandle(pi.hProcess);
}
CloseHandle(event1);
Sleep(1000);
}
return 0;
}
方法2:
本方法使用CreateThread,WaitForSingleObject,CreateMutex与OpenMutex函数
代码:编写一个DWORD WINAPI函数名为ThreadCheckPro,输入参数为进程名,用来检测守护进程是否被关闭。函数中创建hMutex变量,使用while循环判断能否用OpenMutex打开守护的进程,使用if判断,如果不能打开,意味着守护的进程被关闭,于是使用CreateProcess函数在原目录下创建打开守护的进程,打开成功后再使用WaitForSingleObject监控等待进程被关闭的时候,进程被再次关闭时,关闭pi.hProcess、pi.hThread两个句柄,跳出判断结构,Sleep 0.5秒后进入下一次循环,于是便能再次打开守护的进程。
程序2原理与程序1 相同。(在VC中代码编译链接成功后,进入两个守护程序工程的Debug文件中找出exe文件,把它们放在同一个目录下并分别命名为test1与test2。)
守护程序1:
#include <windows.h>
#include<stdio.h>
DWORD WINAPI ThreadCheckProc(LPVOID lParam){
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi={0};
HANDLE hMutex;
char *pName=(char *)lParam;
while(true){
hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,pName);
if(!hMutex){
CreateProcess(pName,
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi );
WaitForSingleObject( pi.hProcess, INFINITE );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
else{
CloseHandle(hMutex);
}
Sleep(500);
}
}
int main()
{
DWORD ThreadId;
CreateMutex(NULL,TRUE,"test1.exe");
CreateThread(NULL,0,ThreadCheckProc,(LPVOID *)"test2.exe",0,&ThreadId);
printf("Protected\n");
while(1){
Sleep(500);
}
return 0;
}
守护程序2
#include <windows.h>
#include<stdio.h>
DWORD WINAPI ThreadCheckProc(LPVOID lParam){
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi={0};
HANDLE hMutex;
char *pName=(char *)lParam;
while(true){
hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,pName);
if(!hMutex){
CreateProcess(pName,
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi );
WaitForSingleObject( pi.hProcess, INFINITE );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
else{
CloseHandle(hMutex);
}
Sleep(500);
}
}
int main()
{
DWORD ThreadId;
CreateMutex(NULL,TRUE,"test2.exe");
CreateThread(NULL,0,ThreadCheckProc,(LPVOID *)"test1.exe",0,&ThreadId);
printf("Protected\n");
while(1){
Sleep(500);
}
return 0;
}
项目测试情况
方法1:
打开protect1程序,protect2被自动打开,并显示守护进程打开成功,进程号为8548
关闭protect1程序,protect2马上把它再度打开,显示守护进程打开成功,进程号为8200
再次关闭protect1,protect1被再度打开
方法2
打开test1,test2立马被打开,两个进程显示protected,相互守护。
任意关闭两者之间的一个程序,另一个作为守护进程又马上把被关闭的程序打开。
总结
进程守护是保护进程不被错误关闭,保证程序能一直运行的常用手段,同时也可能会被恶意代码所利用,恶意代码可以通过远程线程注入,在合法的程序中为自己设置守护进程,只要守护程序没被关闭,恶意代码就能一直运行。通过这次的双进程守护程序设计实验,我学会了如何使用CreateEvent、OpenEvent、CreateMutex、OpenMutex与CreateProcess、CreateThread这些Windows API函数,成功实现了具有相互守护功能的程序。
不足之处是,对这些Windows API函数的理解还不是很透彻,只是依据使用说明依次对齐进行参数的设置,并不清楚它们的实现原理和一些错误处理,在以后的学习中,会更加注重Windows编程的基础学习,弄清各种函数的底层原理,以便能更透彻地掌握各种API的用法,能避免更多不必要的错误。