具体实施方式
本发明实施例提供了一种检测进程实例个数的方法和相关装置,用于更加隐蔽的检测同一时间存在的进程实例的个数。
本申请的说明书和权利要求书及上述附图中的术语“第一”、“第二”、“第三”、“第四”等(如果存在)是用于区别类似的对象,而不必用于描述特定的顺序或先后次序。应该理解这样使用的数据在适当情况下可以互换,以便这里描述的实施例能够以除了在这里图示或描述的内容以外的顺序实施。此外,术语“包括”和“具有”以及他们的任何变形,意图在于覆盖不排他的包含,例如,包含了一系列步骤或单元的过程、方法、系统、产品或设备不必限于清楚地列出的那些步骤或单元,而是可包括没有清楚地列出的或对于这些过程、方法、产品或设备固有的其它步骤或单元。
对于一个PC客户端软件来说,每一个进程都会存在一个默认的输入法窗口,本文则可以基于此,来对所有本文创建的进程的默认输入法的窗口隐藏一些进程相关的信息,那么在后续枚举所有输入法窗口时,如果有多个进程满足本文的隐藏方法,则存在多个实例进程,从而可以起到非常隐蔽的检测当前进程实例的个数的方法。
有鉴于此,请参阅图1,为本发明实施例提供的一种检测进程实例个数的方法的流程图,具体包括:
101、枚举系统的所有窗口,以查找进程的默认输入法窗口;
当本文的PC客户端软件启动后,则会去遍历系统所有的窗口,并从中获取默认输入法的窗口,从而后续则可以将进程相关信息隐藏到默认输入法窗口中。具体实现如下:
首先需要枚举系统的所有窗口。
EnumWindows(SDefaultIMECallBack,(LPARAM)this);
BOOL WINAPI EnumWindows(
_In_WNDENUMPROC lpEnumFunc,
_In_LPARAM lParam
);
其中,lpEnumFunc表示应用程序定义的回调函数的指针;lParam表示传递给回调函数的应用程序定义的值;函数EnumWindows则具有枚举系统所有窗口的功能。
接下来则编写具体的窗口枚举过程。
BOOL CALLBACK SDefaultIMECallBack(HWND hWnd,LPARAM pParam){
同样的首先会通过系统函数GetWindowThreadProcessId则得到了此窗口所属进程的进程唯一标识dwWndPid,具体实现函数如下:
GetWindowThreadProcessId(hWnd,&dwWndPid);
然后通过调用系统函数GetCurrentProcessId来得到本进程的唯一标识,具体实现函数如下:
DWORD dwCurrentPid=GetCurrentProcessId();
如果进程唯一标识不一致则退出,说明此窗口不是本进程的默认输入法窗口,具体实现函数如下:
If(dwCurrentPid!=dwWndPid){return;}
接下来则需要通过系统函数SendMessage获取窗口的名称,窗口名称则存储在szWndName变量中,具体实现函数如下:
SendMessage(hWnd,WM_GETTEXT,MAX_PATH,(LPARAM)szWndName);
接下来则需要判断此窗口名称是否是“defaultIME”,如果是则说明此窗口是默认输入法窗口,具体实现函数如下:
If(strcmp(“defaultIME”,szWndName)!=0){return;}
如果此窗口的名称不是“defaultIME”则说明此窗口不是默认输入法窗口则直接退出。否则说明是输入法窗口。从而在此步骤中则找到了输入法的窗口句柄。
102、将进程的进程唯一标识进行加密,得到加密后的进程唯一标识;
103、根据加密后的进程唯一标识设置默认输入法窗口的坐标位置;
104、存储加密后的进程唯一标识和默认输入法窗口的坐标位置;
在步骤101中获取到了默认输入法的窗口,在此步骤中则需要将进程唯一标识进行加密,以存储到此默认输入法窗口中。而对于本文来说,由于默认输入法窗口是不能存储文本的,那么加密后的进程唯一id如何存储到默认输入法窗口中。本文的创意想法则是的进程唯一ID是一个4个字节的整数数据,而对于每一个窗口来说,都是会设置其窗口的位置坐标的,对于整个屏幕来所,一个坐标则包含水平位置和垂直位置,那么正好需要一个4个字节的整数来存储。则类似于一个坐标系中X的坐标和Y的坐标。
本文则用TEA加密算法来对进程唯一ID进行加密。
在步骤一中获取到了进程的唯一标示dwCurrentPid。
EncryptData=Tea.encrypt(dwCurrentPid,KEY);
其中Tea.encrypt则是加密的接口;dwCurrentPid则是原始的未加密的进程唯一标示是4个字节;KEY则是本文使用的加密的KEY(可以随机生成一个32位的字符串);加密后的数据则是EncryptData,其也是一个4个字节的数据。
得到了加密的进程唯一标识后,则需要将加密的数据进行拆分后来作为窗口的坐标数据来设置窗口的坐标位置。其中EncryptData是一个4个字节的数据,本文将前2个字节拆分成一个数据,后2个字节拆分成另一个数据。
Posx=EncryptdataHead;Posy=EncryptdataEnd;
其中Posx则表示窗口的X坐标;Posy则表示窗口的y坐标;EncryptdataHead则标示EncryptData的前2个字节的数据;EncryptdataEnd则标示EncryptData的后2个字节的数据。接下来则调用系统函数SetWindowPos来设置坐标,函数原型如下:
BOOL
WINAPI
SetWindowPos(
HWND hWnd,
HWND hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
UINT uFlags);
其中,hWnd是窗口的句柄;hWndInsertAfter是窗口Z顺序属性;X是窗口在X轴的位置;Y是窗口在Y辆的位置;cx是窗口的宽度;cy是窗口的高度;uFlags是选择设置的标志。
本文调用如下:
SetWindowPos(hWnd,HWND_BOTTOM,Posx,Posy,0,0,SWP_NOZORDER);
其中窗口句柄hWnd则是之前获取到的默认输入法的窗口;Posx和Posy则是设置窗口的坐标位置。从而本文将进程的唯一标示通过加密和拆分后转换成默认输入法的坐标位置来存储。
105、创建检测线程;
鉴于独立的线程具有更为隐蔽的效果,同时也可以与主程序的逻辑分开,本申请实施例中,通过创建独立的检测线程来查看当前进程的实例个数。具体地,本实施例中使用系统函数CreateThread来创建一个独立的线程函数。其函数原型如下:
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
DWORD lpThreadId,
);
其中,lpThreadAttributes表示线程内核对象的安全属性,一般传入NULL表示使用默认设置,dwStackSize表示线程栈空间大小;lpStartAddress表示新线程所执行的线程函数地址;lpParameter表示是传给线程函数的参数;dwCreationFlags表示指定额外的标志来控制线程的创建,lpThreadId表示将返回线程的ID号。
具体调用如下:
hThread=CreateThread(NULL,0,ThreadFunc,0,0,&threadId);
通过上述过程,本文创建了一个检测线程,并且线程的执行函数是ThreadFunc。那么整个后续的检查逻辑都会编写在线程执行函数ThreadFunc中。
106、在检测线程中从系统中的所有窗口中确定当前进程的默认输入法窗口;
107、获取当前进程的默认输入法窗口的坐标位置;
108、在线程函数中查看进程实例个数。
在步骤105中我们创建了一个线程函数,此线程函数则是用于执行本文的查看当前系统的进程实例个数。具体实现如下:
DWORD WINAPI ThreadFunc(LPVOID p){
本文是通过对每一个进程创建一个编辑本文框来实现对进程信息的记录。那么本文则需要去枚举系统中所有的窗口,并且对文本框的窗口进行检查。
接下来本文来编写函数CheckDefaultIMECallBack来实现对所有窗口进行检查,具体实现函数如下:
EnumWindows(CheckDefaultIMECallBack,(LPARAM)this);
BOOL CALLBACK CheckDefaultIMECallBack(HWND hWnd,LPARAM pParam)
{
首先调用系统函数GetClassName来获取当前窗口的名称,其中获取到的窗口名称则存储在szClassName。具体实现函数如下:
GetClassName(hWnd,szClassName,MAX_PATH);
if(strcmp(“IME”,szClassName)!=0){return;}
然后比较其是不是“IME”,如果不是则说明此窗口不是一个默认输入法的窗口,否则是默认输入法的窗口。
接着定义一个矩阵变量RECT rect={0};然后通过调用系统函数GetWindowRect来获取此窗口的位置。函数原型如下:
BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);
其中参数HWND hWnd表示需要获取的窗口的句柄;参数LPRECT lpRect表示用于存储获取到的窗口坐标信息。
GetWindowRect(hWnd,&rect);
调用完成此函数后,坐标信息则存储在变量rect中;接下来则需要从窗口坐标中解密出原始的进程唯一ID,具体地,得到了窗口的坐标后,将X和Y坐标拼接成一个加密的数据EncryptData,并根据该加密的数据解密出原始的进程唯一ID,具体函数实现如下:
EncryptData=rect.top+rect.left;
其中,rect.top则存储的是此窗口的Y坐标;rect.left则存储的是此窗口的X坐标。接下来则解密数据,具体函数实现如下:
dwCurrentPid=Tea.encrypt(EncryptData,KEY);
其中Tea.decrypt则是解密的接口;EncryptData则是加密后的数据;dwCurrentPid则是原始的未加密的进程唯一标示是4个字节;KEY则是本文使用的解密的KEY(此KEY和加密的KEY是同一个值)。
接下来则需要通过调用系统函数GetWindowThreadProcessId来获取此窗口所属的进程唯一ID:GetWindowThreadProcessId(hWnd,&dwWndPid);其中,进程的唯一标示则存储在变量dwWndPid中。基于此,本文得到了一个窗口的所属的进程唯一标示,并且通过窗口的坐标解密得到了另一个进程唯一标示。
if(dwWndPid==dwCurrentPid){
如果获取的进程唯一ID和通过默认输入法获取的进程唯一ID相等,则说明此默认输入法的窗口是本文之前创建的进程所设置的,说明是本进程的一个实例。从而如果检测到一个窗口则说明有一个实例,如果有多个则说明有多个实例。
另外,本申请实施例中,除了基于默认输入法的窗口来检测进程实例个数外,还提供了其他的检测进程实例个数的方法,例如基于内存映射文件来检测当前进程实例个数或者基于创建隐藏特殊窗口来检测当前进程实例个数,具体包括:
1)基于内存映射文件来检测进程实例个数;
内存映射文件是由一个文件到一块内存的映射,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问,因此实际应用这种,内存文件映射非常适合于用来管理大文件。由于内存映射文件具有用于读取磁盘的文件的功能,而同时本申请中,内存映射文件是可以多个进程共享内存数据,也就是说一个内存映射文件,可以被多个进程所共享和访问及读写,因此内存映射文件是跨进程的。鉴于此,本发明实施例提供了一种基于内存映射文件检测进程实例个数的方法,具体包括:
步骤1、定义将进程写入内存映射文件的数据存储格式;
对于内存映射文件,需要往其中写入数据,同时为了更加的安全和防止与正常的映射文件冲突,本文则定义了一种自定义的写数据的格式即写入内存映射文件的数据存储格式,从而更具有隐蔽性。该数据存储格式中包括以下信息:特征码、进程的进程唯一标识和加密KEY,其中,特征码为一段隐藏字符,特征码表示内存映射文件用于检测程序多开实例,加密KEY用于加密所述进程的进程唯一标识。
具体地,首先在内存映射文件头写入特征码,需要说明的是,此特征码是独有的,表明是本文用于检测程序多开实例的内存映射文件。本申请实施例中,特征码是一段不可见字符,可以有32个字节,例如特征码可以为:0x245631a4d7ea56dc2145632abc2ef365。需要说明的是,特征码可以自定义只要是具有唯一性或者不易于产生碰撞的都可以。
再定义每一个进程写入的数据格式。对于每一个进程需要在内存映射文件中写入该进程的进程唯一标识,同时为了更为的安全,需要对写入的数据进行加密,所以也需要写入一段KEY数据,即加密KEY。同时为了区分出每一个进程写入数据的格式,也需要加入一段特征码。
为便于理解,表1为本申请实施例提供的一种看你的数据存储格式,包括表首先10个字节的特征码,32个字节的加密KEY,16个字节的进程唯一标识(ID,identification),总共58个字节,并且每个进程写入的特征码是一样的。
表1
10字节的特征码 |
32字节的加密KEY |
16字节的进程唯一标识 |
步骤2、设计内存映射文件的名称;
在定义将进程写入内存映射文件的数据存储格式后,接下来设计内存映射的文件名称。由于此名称是整个系统共享的,所以不能与其他进程创建的或者系统创建的名称一致或者冲突,同时也需要对名称进行隐蔽的作用。另外,为了防止该内存映射文件被黑客破解和发现,需要每次开机执行时都会发生变化,同时对于本程序的所有进程又都能通过一致的方式计算得到。
本发明实施例中,将一个开机启动的进程的进程唯一ID,在经过加密后作为内存映射文件的名称的一部分,同时该内存映射文件的名称加上本文的特征码,则生成内存映射文件的唯一的名称;且每次启动电脑时,开机启动的进程的进程唯一ID都会发生变化。本申请实施例中,由于winlogon.exe进程是用于管理用户登录和退出,所以此进程一定会创建也一定会存在,并且每次开机后此进程的进程唯一ID都会发生变化。为便于理解,具体确定winlogon.exe进程的实现可以如下:
定义特征码,其长度可以为10个字节,如0x4578abdcab。接下来则需要通过进程名称来查找到进程的进程唯一ID,具体的,首先通过调用系统函数CreateToolhelp32Snapshot来获取系统的进程快照。函数原型如下:
HANDLE_WINAPI CreateToolHelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID)
其中,dwFlags用于表示指定了获取系统进程快照的类型;th32ProcessID用于表示指向要获取进程快照的ID,获取系统内所有进程快照时是0;
再定义一个存储进程信息的结构,函数原型如下:
HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
PROCESSENTRY32pe32;
pe32.dwSize=sizeof(PROCESSENTRY32);
接下来则需要编写do while循环函数来遍历查找系统的所有进程,并判断进程名称是否为winlogon.exe
需要说明的是,该do while循环中调用了系统函数Process32Next,表示获取下一个进程的数据。函数原型如下:
BOOL Process32Next(
HANDLE hSnapshot,
LPROCESSENTRY32lppe);
其中hSnapshot是由CreateToolHelp32Snapshot函数返回的系统进程快照的句柄;lppe是指向PROCESSENTRY的结构体指针,进程的详细信息保存在结构体中。通过进程的名称判断是不是winlogon.exe进程,若是,则从该winlogon.exe进程的数据结构中获得该winlogon.exe进程的进程唯一标识,并存储于变量th32ProcessID中。
得到winlogon.exe进程的进程唯一标识后,使用HASH算法MD4来对数据计算HASH从而得到内存映射文件的名称Name,具体函数实现如下:
Name=MD4.Create(0x4578abdcab+th32ProcessID)。
步骤3、确定内存映射文件的加密方式;
为了保障数据写入内存映射文件的安全性,需要对数据进行加密存储。本申请实施例中,将加密KEY和加密的结果都写入内存映射文件中,并且保障每次使用的加密KEY都是变化的,从而进一步提高数据的安全性。本文则使用基于微型加密算法(tinyencryption algorithm,TEA)和Blowfish加密算法,即结合快速加密和高强度加密算法,使得破解者很难分析清楚使用的是何种加密算法。具体加密算法可以如下:
首先每次基于当前时间来生成随机种子,具体地:通过函数Time time=Time()获取当前系统时间,再调用系统函数srand来设置随机种子,即srand(time);其次通过函数KEY=Rand()生成一个32为长度的随机数KEY,从而得到了加密使用的加密KEY;在得到加密KEY后,使用加密KEY对数据即进程的进程唯一标识进行加密,如采用Blowfish加密算法进行加密,具体函数实现如下:
Encryptdata=Blowfish.Encrypt(data,KEY);
其中Blowfish.Encrypt则是Blowfish加密算法的加密接口;data则是需要加密的数据,即进程的进程唯一标识;KEY则是需要加密的加密KEY;
采用Blowfish加密算法进行初次加密后,再使用TEA加密算法再次对数据进行加密,具体函数实现如下:
Encryptdata2=TEA.Encrypt(Encryptdata,KEY);
其中TEA.Encrypt则是TEA加密算法的加密接口;KEY则是需要加密的加密KEY;Encryptdata则是经过Blowfish加密算法进行加密后得到的初加密数据;Encryptdata2则是二次加密后输出的次加密数据,即加密后的进程的进程唯一标识。
步骤4、创建内存映射文件;
在之前的步骤准备好后,此步骤则是程序启动后,需要创建一个内存映射文件,并向创建的内存映射文件写入数据。具体创建则是通过windows系统的应用程序编程接口(application programming intereface,API)函数来创建。具体实现如下:hSection=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,SECTION_SIZE,Name);具体地,创建内存映射文件的函数的原型如下:
首先创建内存映射文件。
其中,hFile表示物理文件句柄;lpAttributes表示安全设置;flProtect表示保护设置;dwMaximumSizeHigh表示高位文件大小;dwMaximumSizeLow表示低位文件大小;lpName表示共享内存名称,即是在步骤2中生成的内存映射文件的名称。
接下来则需要获取内存映射文件并获取对应的地址来写入进程的进程唯一标识,具体地,通过调用系统函数MapViewOfFile则可以获取到内存映射文件的头端,从而可以向其中写入数据,具体获取到内存映射文件的头端的函数实现如下:
PBYTE pMap=(PBYTE)MapViewOfFile(hSection,FILE_MAP_ALL_ACCESS,0,0,0);
其中,参数hSection用于表示创建的内存映射文件,
参数FILE_MAP_ALL_ACCESS用于表示该内存映射文件的地址空间。
步骤5、依据数据存储格式,将进程的进程唯一标识写入内存映射文件;
创建了内存映射文件后,按照之前定义的内存映射文件的格式来写入内存映射文件。首先为了标记本映射文件的特殊性,通过系统函数内存拷贝memcpy来将特征码拷贝到内存映射文件头,具体函数实现可如下:Memcpy(pMap,0x245631a4d7ea56dc2145632abc2ef365,32),其中,与步骤1类似,本步骤中,也可以将特征码设置为0x245631a4d7ea56dc2145632abc2ef365,32用于表示该特征码为32字节;接下来需要按照表1类似的格式写入进程的进程唯一标识,为获取该进程的进程唯一标识,可以采用GetCurrentProcessId()函数,其中,GetCurrentProcessId()函数用于获取当前进程的唯一标识符,其返回值即为当前的进程标识符,即本申请实施例中,首先调用系统函数GetCurrentProcessId()来获取进程唯一标识符,其返回值则是获取到的进程唯一标识。具体实现如下:
DWORD dwProcessId=GetCurrentProcessId();
在得到了进程的进程唯一标识后,需要对其进行加密后再写入到内存映射文件中。如何加密使得数据更具有隐蔽性,本申请实施例中可以采取之前获取的winlogon.exe进程的进程唯一标识,此标识会随着每次开机都会发生变化,同时本文还可采取当前时间戳信息,从而每个时刻的值都会发生变化,将这些数据拼接到一起从而通过计算HASH值而得到最终加密使用的加密KEY,从而保障了加密KEY的变化性。
在上述步骤中可获取到winlogon.exe进程的进程唯一标识th32ProcessID,然后调用系统函数Long time=Time()来获取当前时间戳信息;可选的,本申请实施例中,可采用算法SHA-1来计算后续需要加密需要使用的加密KEY,即KEY=SHA-1.Create(th32ProcessID+time)。在得到加密KEY后,接下来则分别使用Blowfish加密算法和TEA加密算法来对进程的进程唯一标识进行加密,具体实现函数如下:
Encryptdata=Blowfish.Encrypt(dwProcessId,KEY);
其中Blowfish.Encrypt则是Blowfish加密算法的加密接口;dwProcessId是获取的本进程的进程唯一标识;KEY则是需要加密的加密KEY;
采用Blowfish加密算法进行初次加密后,再使用TEA加密算法再次对数据进行加密,具体函数实现如下:
Encryptdata2=TEA.Encrypt(Encryptdata,KEY);
其中TEA.Encrypt则是TEA加密算法的加密接口;KEY则是需要加密的加密KEY;Encryptdata则是经过Blowfish加密算法进行加密后得到的初加密数据;Encryptdata2则是二次加密后输出的次加密数据,即加密后的进程的进程唯一标识。可以理解的是,通过采用快速加密算法如TEA加密算法和高强度加密算法如Blowfish加密算法,使得破解者很难分析清楚使用的是何种加密算法,大大增加了破解者破解的难度,提高了安全性。
计算得到加密后的进程的进程唯一标识后,则需要将其写入到内存映射文件中,其中可以先将特征码已经写入内存映射文件的头端,接下来写入加密KEY和加密后的进程的进程唯一标识,本申请实施例中,可以通过内存拷贝系统函数Memcpy(void*dest,constvoid*src,size_t n)将加密KEY和加密后的进程的进程唯一标识分别写入,其中,函数Memcpy()用于从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。具体地,通过系统函数Memcpy(pMap+10,KEY,32)实现加密KEY的写入;通过系统函数Memcpy(pMap+10+32,EncryptData2,32)实现加密后的进程的进程唯一标识的写入。如果后续有其他进程启动,则会将信息写入到本进程的后面。
步骤6、检测当前进程实例的个数。
在任意时刻,本进程启动后,都可以去读取内存映射文件,从而遍历内存映射文件的数据,查看其存在多少个内存映射文件的进程信息,对于正常的一个实例来说,则只会存在一份。如果该进程启动了多份实例则会存在多个进实例,从而可以得到具体启动了多少个进程实例。具体读取方法则是按照写入的格式去读取相关的进程唯一标识;如果该标识不是自己的进程唯一标识,说明当前同时存在多个进程唯一标识。具体地,则是读取内存映射文件然后对数据进行解密,具体函数实现如下:
EncryptData=TEA.decrypt(EncryptData2,KEY);
dwProcessId=Blowfish.decrypt(EncryptData,KEY);
其中TEA.decrypt则是TEA加密算法的解密算法;Blowfish.decrypt则是Blowfish加密算法的解密算法,KEY即加密使用的加密KEY则可以从内存映射文件中读取到,EncryptData2是加密的进程唯一标识,dwProcessId则得到原始的进程唯一标识。
需要说明的是,在实际检查的情况下,本文发现有的时候由于程序出现故障,在退出时或者在运行时,程序会处于卡死状态,从而会导致误判。那么为了更为准确的检查当前系统是否有多个实例的进程在运行,那么本文则需要对在步骤6中检查到的进程的唯一标识去判断其是否是处于卡死状态。具体实现如下:
首先通过调用系统函数GetModuleHandle获取系统ntdll.dll的模块句柄,具体实现函数如下:
HMODULE hNtdll=GetModuleHandle(“ntdll.dll”);
函数原型如下:
HMODULE WINAPI GetModuleHandle(
_In_opt_LPCTSTR lpModuleName
);
即输入参数是模块的名称,返回值则是该模块ntdll.dll的模块句柄。
接下来再通过系统函数GetProcAddress从系统ntdll.dll的模块中获取系统函数NtQueryInformationProcess,其中函数NtQueryInformationProcess用于将指定类型的进程信息拷贝到某个缓冲,故本申请实施例中,可以通过此函数NtQueryInformationProcess获取到进程相关的信息。
DWORD dwFunc=GetProcAddress(hNtdll,“NtQueryInformationProcess”);
其中函数原型如下:
FARPROC GetProcAddress(
HMODULE hModule;
LPCSTR lpProcName;
);
其中,hModule标识DLL模块句柄,lpProcName表示函数名,返回值则是模块的句柄。
获得模块的句柄后,接下来再通过系统函数OpenProcess打开一个已存在的进程对象,并返回进程的句柄。具体函数实现如下:
hProc=OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,dwProcessId);
其中函数原型如下:
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId,
);
其中,dwDesiredAccess表示渴望得到的访问权限(标志),bInheritHandle表示是否继承句柄,dwProcessId表示进程标识符。
得到进程句柄后,再定义一个变量来存储获取到的进程信息,具体实现函数如下:
PROCESS_BASIC_INFORMATION info={0};
ZwQueryInformationProcess(hProc,ProcessBasicInformation,&info,sizeof(PROCESS_BASIC_INFORMATION),NULL);
其中参数hProc则是之前获取到的进程句柄;参数ProcessBasicInformation则是需要获取进程的基本信息;参数info则是用户获取到的信息的存储变量。
在获取到了进程相关信息后,则可以通过该进程相关信息判断进程的状态,具体实现函数如下:
其中,如果进程状态是STATUS_PENDING,则表示进程处于卡死状态,否则当进程是活跃的。因此通过此功能,可以排除一些误判的可能性。
该实施例中,对于每一个进程创建实例时,都会创建一个独特固定的内存映射文件,并且为了防止于正常程序冲突,本文则取名非常独特不会产生和其他程序碰撞的可能性。本文通过将进程的进程唯一ID加密后写入内存映射文件,从而后续程序读取时,可以查找当前映射文件中有几份进程唯一ID,如果存在多份则认为当前有多个实例。基于本文的方法不仅仅更为隐蔽,同时还可以检测到同一时间存在的实例个数,并且误判的可能性更小。
2)基于创建隐藏特殊窗口来检测进程实例个数;
对于一个PC客户端软件来说,是可以创建窗口的,那么并且创建的窗口具有系统级别,其他进程也可以访问和枚举得到。那么本文的思路则是创建一个文本框,也会是系统的一个窗口,然后为了不影响正常的程序,同时能够很好的隐藏的效果。本文则将其设置为不可见,从而从用户的角度是看不到此窗口的。同时为了能够起到检测当前创建的进程实例个数,本文则将进程的唯一ID写入到创建的文本窗口中。对于检测时则可以判断该窗口的进程和写入窗口中的文本的进程ID是否一致,如果一致则说明是一个本文的进程实例,如果有多个则说明存在多个实例。因此通过窗口则属于比较隐蔽的方法,同时也不会影响正常的程序。
本步骤主要描述了基于内存映射文件来检测当前进程实例个数的方法。具体的设计步骤如下所示:
步骤1、设计程序启动时创建隐藏的文本控件窗口。
对于本文的检测方法则是通过创建一个文本控件窗口,并对其进行隐藏,从而起到隐蔽和不影响正常的程序。其中创建文本控件窗口本文调用系统函数CreateWindowW来进行创建。函数原型如下:
其中,lpClassName表示指向一个空结束的字符串或整型数,lpWindowName表示指向一个指定窗口名的空结束的字符串指针,dwstyle表示指定创建窗口的风格,X表示指定窗口的初始水平位置,Y表示指定窗口的初始垂直位置,nWidth表示以设备单元指明窗口的宽度,nHeight表示以设备单元指明窗口的高度,hWndParent表示指向被创建窗口的父窗口或所有者窗口的句柄,hMenu表示菜单句柄,或依据窗口风格指明一个子窗口标识,hInstance表示与窗口相关联的模块事例的句柄,lpParam表示指向一个值的指针,该值传递给窗口WM_CREATE消息。
另外本文创建的窗口的名称是一个文本控件,所以本文取名为"EDIT";窗口的名称本文则设置为空字符串;而其他窗口坐标和大小本文则设置为0。而其中需要一个窗口的实例句柄,本文则获取当前进程的句柄GetModuleHandleA,其中传入参数为空标示获取本模块的句柄。
步骤2、设计创建隐藏的文本控件窗口的帮助文本。
对于正常的程序来说,系统的文本控件非常的多,为了防止产生误判,本文则将创建的文本控件的窗口的帮助文本设置为“EDIT”,从而区分出正常的窗口,因为不会有正常程序将窗口的帮助文本设置为“EDIT”,当然也可以是其他的字符串名称,但是最大长度必须是4个字节。
通过系统函数SetWindowContextHelpId来设置一个窗口的帮助文本。
其函数原型如下:
BOOL SetWindowContextHelpId(
DWORD dwContextHelpId
);
其中dwContextHelpId则为设置的一个4个字节的数据,对应本文则是“EDIT”。
SetWindowContextHelpId(hEdit,'EDIT');其中hEdit则是在步骤1中创建的文本控件的窗口句柄。
步骤3、设计获取进程唯一标识进行加密。
有了窗口后,本文需要将当前进程的唯一标示进行加密后,写入到窗口的文本中。
首先调用系统函数来获取进程唯一标识符。
DWORD dwProcessId=GetCurrentProcessId();
函数原型如下:
DWORD GetCurrentProcessId(VOID);
其返回值则是获取到的进程唯一标识。
接下来则使用KEY对数据进行加密,具体实现函数如下:
Encryptdata=Des.Encrypt(dwProcessId,KEY);
其中Des.Encrypt则是加密算法的接口,dwProcessId则是需要加密的数据,其中KEY则是需要加密的KEY值。
本文在此使用固定的KEY来进行加密。此KEY可以定义一个32位的特征字符串,如:“abcd1122334455667788aabbccddabc”,从而最终我们得到了加密后的进程唯一标识符Encryptdata。
步骤4、向文本窗口中写入加密后的进程唯一标识。
得到了加密后的字符串后,我们则可以将加密的值写入到创建的文本控件中。我们可以通过调用系统函数SendMessageW来对该控件发送消息,从而实现写入数据。
函数原型如下:
SendMessageW(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);
其中hWnd表示窗口程序将接收消息的窗口的句柄;Msg表示指定被发送的消息;wParam表示指定附加的消息特定信息;IParam表示指定附加的消息特定信息。
SendMessageW(hEdit,WM_SETTEXT,MAX_PATH,(LPARAM)Encryptdata);
其中本文则是对创建的文本控件窗口发送写入文本的消息;hEdit则是之前创建的文本控件;Encryptdata则是加密后的数据。
步骤5、通过创建独立的线程来查看当前进程实例个数。
本文通过创建独立的线程来查看当前进程的实例个数,独立的线程具有更为隐蔽的效果,同时也可以与主程序的逻辑分开。具体本文则使用系统函数CreateThread来创建一个独立的线程函数。其函数原型如下:
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
DWORD lpThreadId,
);
其中,dwStackSize表示线程栈空间大小;lpStartAddress表示新线程所执行的线程函数地址;lpParameter表示是传给线程函数的参数;dwCreationFlags表示指定额外的标志来控制线程的创建,lpThreadId表示将返回线程的ID号。
具体调用如下:
hThread=CreateThread(NULL,0,ThreadFunc,0,0,&threadId);
其中本文创建了一个线程,并且线程的执行函数是ThreadFunc。那么整个后续的检查逻辑都会编写在线程执行函数ThreadFunc中。
步骤6、在线程函数中查看进程实例个数。
在步骤5中我们创建了一个线程函数,此线程函数则是用于执行本文的查看当前系统的进程实例个数。具体实现如下:
DWORD WINAPI ThreadFunc(LPVOID p){
本文是通过对每一个进程创建一个编辑本文框来实现对进程信息的记录。那么本文则需要去枚举系统中所有的窗口,并且对文本框的窗口进行检查。
EnumWindows(CheckEditCallBack,(LPARAM)this);
函数原型:
BOOL WINAPI EnumWindows(
_In_WNDENUMPROC lpEnumFunc,
_In_LPARAM lParam
);
其中,lpEnumFunc表示应用程序定义的回调函数的指针;lParam表示传递给回调函数的应用程序定义的值;函数EnumWindows表示具有枚举系统所有窗口的功能。
接下来则编写具体的窗口枚举过程。
BOOL CALLBACK CheckEditCallBack(HWND hWnd,LPARAM pParam){
首先我们需要获取窗口的名称,通过调用系统函数GetClassName来获取。
GetClassName(hWnd,szClassName,MAX_PATH);
并且需要判断窗口文本是不是本文创建的“EDIT”。
再获取窗口的帮助文本
DWORD dwWindowHelpId=GetWindowContextHelpId(hWnd);
接下来判断该窗口是不是我们之前创建的窗口。
Bool bV=IsWindowVisible(hWnd);
首先通过系统函数IsWindowVisible来判断窗口是否可见,如果可见则不是本文创建的窗口。
If(bV){return;}
接下来判断帮助的文本:如果文本不是'EDIT'则不是本文创建的窗口。
If(dwWindowHelpId!='EDIT'){return;}
接下来我们需要获取文本编辑框的文本,类似的可以通过发送消息SendMessageW的方式来获取,具体实现函数如下:
SendMessageW(hWnd,WM_GETTEXT,MAX_PATH,(LPARAM)wzWndName);
通过上述调用则得到了文本控件的文本wzWndName。之前存储时数据是加密存储,那么本文则对此数据进行解密,具体解密操作实现如下:
dwProcessId=Des.Decrypt(Encryptdata,KEY);
其中Des.Decrypt则是解密算法的接口;dwProcessId则是解密后得到的进程唯一标示;KEY则是需要解密的KEY值,此值则和加密时使用的值相同。
接下来再获取此窗口对应的进程唯一标示,具体实现函数如下:
GetWindowThreadProcessId(hWnd,&dwWndPid);
函数原型如下:
DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);
其中,hWnd表示窗口的句柄;lpdwProcessId表示得到的进程唯一标识符。从而我们对比lpdwProcessId和dwProcessId的值一致则说明此窗口确实是我们之前创建进程时创建的,那么则说明查找到了一个进程的实例。如果查找到多个则说明存在错过进程实例。
上面从检测进程实例个数的方法的角度对本发明实施例进行了描述,下面从检测装置的角度对本发明实施例进行描述。
请参阅图2,图2为本发明实施例提供的一种可能的检测装置的实施例示意图,其中,该服务器具体包括:
查找单元201,用于枚举系统的所有窗口,以查找进程的默认输入法窗口;
加密单元202,用于将所述进程的进程唯一标识进行加密,得到加密后的进程唯一标识;
设置单元203,用于根据所述加密后的进程唯一标识设置所述默认输入法窗口的坐标位置;
存储单元204,用于存储所述加密后的进程唯一标识和所述默认输入法窗口的坐标位置;
创建单元205,用于创建检测线程,所述检测线程用于检测当前进程实例的个数;
确定单元206,用于当需要检测当前进程实例的个数时,在所述检测线程中从系统中的所有窗口中确定所述当前进程的默认输入法窗口;
获取单元207,用于获取所述当前进程的默认输入法窗口的坐标位置;
解密单元208,用于根据所述当前进程的默认输入法窗口的坐标位置解密所述加密后的进程唯一标识,得到解密后的进程唯一标识,所述解密后的进程唯一标识的个数为所述当前进程实例的个数。
请参阅图3,图3为本发明实施例提供的电子设备的实施例示意图。
如图3所示,本发明实施例提供了一种电子设备,包括存储器310、处理器320及存储在存储器320上并可在处理器320上运行的计算机程序311,处理器320执行计算机程序311时实现以下步骤:枚举系统的所有窗口,以查找进程的默认输入法窗口;将所述进程的进程唯一标识进行加密,得到加密后的进程唯一标识;根据所述加密后的进程唯一标识设置所述默认输入法窗口的坐标位置;存储所述加密后的进程唯一标识和所述默认输入法窗口的坐标位置;创建检测线程,所述检测线程用于检测当前进程实例的个数;当需要检测当前进程实例的个数时,在所述检测线程中从系统中的所有窗口中确定所述当前进程的默认输入法窗口;获取所述当前进程的默认输入法窗口的坐标位置;根据所述当前进程的默认输入法窗口的坐标位置解密所述加密后的进程唯一标识,得到解密后的进程唯一标识,所述解密后的进程唯一标识的个数为所述当前进程实例的个数。
可选的,在一种可能的实施例中,所述处理器320具体用于:通过调用系统函数EnumWindows枚举所述系统的所有窗口;通过调用系统函数GetWindowThreadProcessId获得窗口所属进程的进程唯一标识;通过调用系统函数GetCurrentProcessId获得所述进程的进程唯一标识;当所述进程的进程唯一标识与所述窗口所属进程的进程唯一标识相同时,通过调用系统函数SendMessage获取所述窗口的名称;当所述窗口的名称为“defaultIME”时,则确定所述窗口为所述进程的默认输入法窗口。
可选的,在一种可能的实施例中,所述处理器320具体用于:随机生成加密KEY;将所述进程的进程唯一标识和所述加密KEY作为分组加密算法TEA的加密接口TEA.Encrypt的输入,以输出所述加密后的进程唯一标识。
可选的,在一种可能的实施例中,所述处理器320具体用于:将所述加密后的进程唯一标识进行拆分,得到前面字节数据和后面字节数据,所述前面字节数据用于表示所述默认输入法窗口在X轴的坐标,所述后面字节数据用于表示所述默认输入法窗口在Y轴的坐标;通过调用系统函数SetWindowsPos设置所述默认输入法窗口的坐标位置,所述前面字节数据和所述后面字节数据为所述系统函数SetWindowsPos的输入参数。
可选的,在一种可能的实施例中,所述处理器320具体用于:将X轴的目标坐标和Y轴的目标坐标拼接成所述当前进程的加密后的进程唯一标识,所述X轴的目标坐标和所述Y轴的目标坐标为所述当前进程的默认输入法窗口的坐标位置;将所述当前进程的加密后的进程唯一标识和所述加密KEY作为所述TEA算法的解密接口TEA.Decrypt的输入,以输出所述解密后的进程唯一标识。
可选的,在一种可能的实施例中,所述处理器320还用于:通过调用系统函数CreatTread创建所述检测线程,所述系统函数CreatTread的输入包括所述检测线程的标识信息,所述检测线程的执行函数为系统函数TreadFunc,所述系统函数TreadFunc用于检测当前进程实例的个数。
可选的,在一种可能的实施例中,所述处理器320具体用于:将函数CheckDefaultIMECallBack作为系统函数EnumWindows的输入,以检查所述系统的所有窗口,所述函数CheckDefaultIMECallBack用于实现所述所有窗口的检查,所述系统函数EnumWindows用于枚举所述系统的所有窗口;通过调用系统函数GetClassName获取并存储所述当前窗口的窗口名称;若所述当前窗口的窗口名称为“IME”,则确定所述当前窗口为所述当前进程的默认输入法窗口。
由于本实施例所介绍的电子设备为实施本发明实施例中一种检测装置中所采用的设备,故而基于本发明实施例中所介绍的方法,本领域所属技术人员能够了解本实施例的电子设备的具体实施方式以及其各种变化形式,所以在此对于该电子设备如何实现本发明实施例中的方法不再详细介绍,只要本领域所属技术人员实施本发明实施例中的方法所采用的设备,都属于本发明所欲保护的范围。
请参阅图4,图4为本发明实施例提供的一种计算机可读存储介质的实施例示意图。
如图4所示,本实施例提供了一种计算机可读存储介质400,其上存储有计算机程序411,该计算机程序411被处理器执行时实现如下步骤:枚举系统的所有窗口,以查找进程的默认输入法窗口;将所述进程的进程唯一标识进行加密,得到加密后的进程唯一标识;根据所述加密后的进程唯一标识设置所述默认输入法窗口的坐标位置;存储所述加密后的进程唯一标识和所述默认输入法窗口的坐标位置;创建检测线程,所述检测线程用于检测当前进程实例的个数;当需要检测当前进程实例的个数时,在所述检测线程中从系统中的所有窗口中确定所述当前进程的默认输入法窗口;获取所述当前进程的默认输入法窗口的坐标位置;根据所述当前进程的默认输入法窗口的坐标位置解密所述加密后的进程唯一标识,得到解密后的进程唯一标识,所述解密后的进程唯一标识的个数为所述当前进程实例的个数。
可选的,在一种可能的实施例中,该计算机程序411被处理器执行时具体用于实现如下步骤:通过调用系统函数EnumWindows枚举所述系统的所有窗口;通过调用系统函数GetWindowThreadProcessId获得窗口所属进程的进程唯一标识;通过调用系统函数GetCurrentProcessId获得所述进程的进程唯一标识;当所述进程的进程唯一标识与所述窗口所属进程的进程唯一标识相同时,通过调用系统函数SendMessage获取所述窗口的名称;当所述窗口的名称为“defaultIME”时,则确定所述窗口为所述进程的默认输入法窗口。
可选的,在一种可能的实施例中,该计算机程序411被处理器执行时具体用于实现如下步骤:随机生成加密KEY;将所述进程的进程唯一标识和所述加密KEY作为分组加密算法TEA的加密接口TEA.Encrypt的输入,以输出所述加密后的进程唯一标识。
可选的,在一种可能的实施例中,该计算机程序411被处理器执行时具体用于实现如下步骤:将所述加密后的进程唯一标识进行拆分,得到前面字节数据和后面字节数据,所述前面字节数据用于表示所述默认输入法窗口在X轴的坐标,所述后面字节数据用于表示所述默认输入法窗口在Y轴的坐标;通过调用系统函数SetWindowsPos设置所述默认输入法窗口的坐标位置,所述前面字节数据和所述后面字节数据为所述系统函数SetWindowsPos的输入参数。
可选的,在一种可能的实施例中,该计算机程序411被处理器执行时具体用于实现如下步骤:将X轴的目标坐标和Y轴的目标坐标拼接成所述当前进程的加密后的进程唯一标识,所述X轴的目标坐标和所述Y轴的目标坐标为所述当前进程的默认输入法窗口的坐标位置;将所述当前进程的加密后的进程唯一标识和所述加密KEY作为所述TEA算法的解密接口TEA.Decrypt的输入,以输出所述解密后的进程唯一标识。
可选的,在一种可能的实施例中,该计算机程序411被处理器执行时还用于实现如下步骤:通过调用系统函数CreatTread创建所述检测线程,所述系统函数CreatTread的输入包括所述检测线程的标识信息,所述检测线程的执行函数为系统函数TreadFunc,所述系统函数TreadFunc用于检测当前进程实例的个数。
可选的,在一种可能的实施例中,该计算机程序411被处理器执行时具体用于实现如下步骤:将函数CheckDefaultIMECallBack作为系统函数EnumWindows的输入,以检查所述系统的所有窗口,所述函数CheckDefaultIMECallBack用于实现所述所有窗口的检查,所述系统函数EnumWindows用于枚举所述系统的所有窗口;通过调用系统函数GetClassName获取并存储所述当前窗口的窗口名称;若所述当前窗口的窗口名称为“IME”,则确定所述当前窗口为所述当前进程的默认输入法窗口。
需要说明的是,在上述实施例中,对各个实施例的描述都各有侧重,某个实施例中没有详细描述的部分,可以参见其它实施例的相关描述。
本领域内的技术人员应明白,本发明的实施例可提供为方法、系统、或计算机程序产品。因此,本发明可采用完全硬件实施例、完全软件实施例、或结合软件和硬件方面的实施例的形式。而且,本发明可采用在一个或多个其中包含有计算机可用程序代码的计算机可用存储介质(包括但不限于磁盘存储器、CD-ROM、光学存储器等)上实施的计算机程序产品的形式。
本发明是参照根据本发明实施例的方法、设备(系统)、和计算机程序产品的流程图和/或方框图来描述。应理解可由计算机程序指令实现流程图和/或方框图中的每一流程和/或方框、以及流程图和/或方框图中的流程和/或方框的结合。可提供这些计算机程序指令到通用计算机、专用计算机、嵌入式计算机或者其他可编程数据处理设备的处理器以产生一个机器,使得通过计算机或其他可编程数据处理设备的处理器执行的指令产生用于实现在流程图一个流程或多个流程和/或方框图一个方框或多个方框中指定的功能的装置。
这些计算机程序指令也可存储在能引导计算机或其他可编程数据处理设备以特定方式工作的计算机可读存储器中,使得存储在该计算机可读存储器中的指令产生包括指令装置的制造品,该指令装置实现在流程图一个流程或多个流程和/或方框图一个方框或多个方框中指定的功能。
显然,本领域的技术人员可以对本发明进行各种改动和变型而不脱离本发明的精神和范围。这样,倘若本发明的这些修改和变型属于本发明权利要求及其等同技术的范围之内,则本发明也意图包括这些改动和变型在内。