程序防多开之六:隐藏文件检测

        在前面几章介绍了关于程序限制多开与检测多开数量的文章,可以精准检测到应用程序开启的数量.

        程序防多开之一:进程数量检测

        程序防多开之二:程序窗口检测

        程序防多开之三:全局对象检测

        程序防多开之四:共享区间检测

        程序防多开之五:映射内存检测

        本章介绍一种新的方式可以实现对程序多开检测.

        使用隐藏文件进行检测的原理很简单:

        在硬盘中创建一个文件,作为共享读写数据的介媒,使多个应用程序从中读写数据能够数据交互,并且检查程序的启动数量是否超过限制.

         这种方式的好处是不用创建共享内存和映射内存,直接对文件进行读写,避开了某些作弊者通过类似于修改内存属性\解除映射\挂钩修改映射名字等手段实现的躲避检测.

        正常情况下,如果在硬盘直接创建一个文件,很容易被作弊者发觉并且篡改数据.

        所以我们需要用一些特殊的手段,对被创建的共享文件和数据进行保护.

        首先介绍一下创建(打开)文件的API.

        微软定义的API接口如下:

        

HANDLE CreateFileA(
  [in]           LPCSTR                lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);

        

参数

[in] lpFileName

要创建或打开的文件或设备的名称

[in] dwDesiredAccess

请求对文件或设备的访问权限

[in] dwShareMode

请求的文件或设备的共享模式

[in, optional] lpSecurityAttributes

指向 SECURITY_ATTRIBUTES 结构的指针,该结构包含两个独立但相关的数据成员:可选的安全描述符,以及一个布尔值,用于确定是否可由子进程继承返回的句柄

[in] dwCreationDisposition

要对存在或不存在的文件或设备执行的操作

[in] dwFlagsAndAttributes

文件或设备属性和标志 

[in, optional] hTemplateFile

具有 GENERIC_READ 访问权限的模板文件的有效句柄

        在创建文件时,我们需要用到这个API的各个参数,做到以下事情.

        1.文件是隐藏的,不可见的.

        2.使用完毕后,文件将会删除,不遗留在硬盘之上

        3.文件是共享的,其他程序可以读写程序.

        4.文件属性是系统文件,不允许随意右键删除.

        所以我们需要用以下方式来实现:

        

hFile = CreateFileA(
					szFileName,							// 文件名  
					dwDesiredAccess,		// 读写访问权限  
					FILE_SHARE_READ | FILE_SHARE_WRITE| FILE_SHARE_DELETE, // 共享读写删除 
					NULL,								// 安全属性指针(NULL表示使用默认安全属性)  
					dwCreationDisposition,				// 如果文件不存在则创建它  
					FILE_ATTRIBUTE_HIDDEN| FILE_FLAG_DELETE_ON_CLOSE| FILE_ATTRIBUTE_SYSTEM| FILE_ATTRIBUTE_NORMAL,	// 文件属性  
					NULL								// 模板文件句柄(NULL表示不使用模板文件)  
				);
				/*文件属性解释
				* FILE_ATTRIBUTE_HIDDEN	=	文件被隐藏。 
				* FILE_FLAG_DELETE_ON_CLOSE	=	文件在其所有句柄都关闭后立即被删除,其中包括指定的句柄和任何其他程序打开或重复的句柄。
				* FILE_ATTRIBUTE_SYSTEM =	该文件是操作系统的一部分或由操作系统独占使用
				*/

        上面的代码刚好可以实现我们的要求.

        之后我们还需要做到一件事情:数据加密存放.

        毕竟文件是存放在硬盘之中,如果被居心不良的人随随便便能够查询文件内容,那么我们做这一切的意义就不存在了.

        所以我们在这里定义一个简单的加解密函数,用来对文件数据进行加密和解密.

        

//简单加解密函数,使用异或算法实现
VOID DataCryption(BYTE* pByte,DWORD nSize)
{
	for (size_t i = 0; i < nSize; i++)
	{
		pByte[i] ^= (nSize%0xFF);
	}

}

        当然您可以使用其他加解密算法实现,具体根据您的需求.

         现在我们开始实现对文件的加密读写.

代码:

        



#define FILE_HIDDEN_DELETE_NAME "./MyHiddenDeleteFile.dll"  // 文件具名用.dll后缀以欺骗坏人,可以尝试其他后缀

int FileIsExist(LPCSTR szFileName) 
{
	DWORD fileAttributes = GetFileAttributesA(szFileName);
	if (fileAttributes == INVALID_FILE_ATTRIBUTES) 
	{
		// 文件不存在  
		return 0;
	}
	else {
		// 文件存在  
		return 1;
	}
}

VOID DataCryption(BYTE* pByte,DWORD nSize)
{
	for (size_t i = 0; i < nSize; i++)
	{
		pByte[i] ^= (nSize%0xFF);
	}

}


//读写具有隐藏并且关闭自删除属性的文件
HANDLE WriteReadFileData(HANDLE hFile, LPCSTR szFileName, PBYTE pData, DWORD nSize, BOOL isWrite)
{
	DWORD nRet = 0;
	__try
	{
		do
		{
			if (hFile == NULL || hFile == INVALID_HANDLE_VALUE)
			{
				int isExist = FileIsExist(szFileName);
				DWORD dwCreationDisposition = isExist ? OPEN_EXISTING : OPEN_ALWAYS;
				DWORD dwDesiredAccess = isWrite ? GENERIC_ALL : GENERIC_READ| FILE_SHARE_WRITE;
				hFile = CreateFileA(
					szFileName,							// 文件名  
					dwDesiredAccess,		// 读写访问权限  
					FILE_SHARE_READ | FILE_SHARE_WRITE| FILE_SHARE_DELETE, // 共享读写删除 
					NULL,								// 安全属性指针(NULL表示使用默认安全属性)  
					dwCreationDisposition,				// 如果文件不存在则创建它  
					FILE_ATTRIBUTE_HIDDEN| FILE_FLAG_DELETE_ON_CLOSE| FILE_ATTRIBUTE_SYSTEM| FILE_ATTRIBUTE_NORMAL,	// 文件属性  
					NULL								// 模板文件句柄(NULL表示不使用模板文件)  
				);
				/*文件属性解释
				* FILE_ATTRIBUTE_HIDDEN	=	文件被隐藏。 
				* FILE_FLAG_DELETE_ON_CLOSE	=	文件在其所有句柄都关闭后立即被删除,其中包括指定的句柄和任何其他程序打开或重复的句柄。
				* FILE_ATTRIBUTE_SYSTEM =	该文件是操作系统的一部分或由操作系统独占使用
				*/
			}

			if (hFile==NULL || hFile == INVALID_HANDLE_VALUE )
				break;
			//设置文件指针为文件头部,从头开始读取数据
			SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
			
			DWORD nrwBytes = 0;

			//读写共享内存中数据
			if (isWrite)
				nRet = WriteFile(hFile, pData, nSize, &nrwBytes, NULL);
			else
				nRet =ReadFile(hFile, pData, nSize, &nrwBytes, NULL);

		} while (FALSE);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) { ; }


	if (nRet==NULL && hFile !=NULL && hFile!= INVALID_HANDLE_VALUE)
	{
		CloseHandle(hFile);
		hFile = NULL;
	}
		

	return hFile;
}

LONG GetProcessCountByFileData(HANDLE& hFile)
{
	hFile = NULL;
	//获取自身进程完整路径
	DWORD nCurrPID = GetCurrentProcessId();
	WCHAR strCurrPath[MAX_PATH] = { 0 };
	DWORD nRet = myGetProcessPath(nCurrPID, strCurrPath, sizeof(strCurrPath));
	if (!nRet)
	{
		//获取自身进程完整路径失败!
		return -1;
	}

	DWORD PidArray[10] = { 0 };
	hFile = WriteReadFileData(NULL, FILE_HIDDEN_DELETE_NAME, (PBYTE)PidArray, sizeof(PidArray), FALSE);
	if (hFile == NULL || hFile == INVALID_HANDLE_VALUE)
	{
		//读写文件失败!
		return -1;
	}

	DataCryption((BYTE*)PidArray, sizeof(PidArray));


	//取出读取到的文件数据指针和长度
	DWORD* ppPidArray = PidArray;
	DWORD nBufLen = sizeof(PidArray);

	LONG nCount = 0;
	int nWriteIndex = -1;//用来标记写入共享区域内存的数组下标
	//通过数据长度,解析出数组成员数
	DWORD nPidConts = nBufLen / sizeof(DWORD);
	__try
	{
		//遍历PID数组
		for (size_t i = 0; i < nPidConts; i++)
		{
			DWORD nPid = ppPidArray[i];

			//如果当前进程PID已经记录在隐藏文件中,则设置写入下标为最大数+1(用是否数组越界判断是否写入)
			if (nPid == nCurrPID)
				nWriteIndex = nPidConts + 1;



			if (nPid == 0)
			{
				//仅修改一次写入下标为当前索引
				if (nWriteIndex < 0)
					nWriteIndex = i;
				continue;
			}


			//查询当下PID所在路径是否和本进程路径相同
			WCHAR strPath[MAX_PATH] = { 0 };
			DWORD nRet = myGetProcessPath(nPid, strPath, sizeof(strPath));
			if (!nRet)
			{	//如果进程路径不相同,可能是旧进程关闭,这里记录索引,以便在数组写满后可以覆写数据
				if (nWriteIndex < 0)
					nWriteIndex = i;
			}
			if (nRet)
			{
				if (wcscmp(strPath, strCurrPath) == 0)
				{
					nCount++;
				}
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER) { ; }

	//判断是否在文件数据进程数组中写入本进程PID
	if (nWriteIndex < nPidConts && nWriteIndex >= 0)
	{
		//如果尚未写入,则复制全部进程数组并且写入到文件数据当中
		PidArray[nWriteIndex] = nCurrPID;
		DataCryption((BYTE*)PidArray, sizeof(PidArray));
		nCount++;
		hFile = WriteReadFileData(hFile, FILE_HIDDEN_DELETE_NAME, (PBYTE)PidArray, sizeof(PidArray), TRUE);
		if (!hFile)
			return -1;
	}


	return nCount;
}

        代码解释:

        首先我们通过#define FILE_HIDDEN_DELETE_NAME "./MyHiddenDeleteFile.dll"  // 文件具名用.dll后缀以欺骗坏人,可以尝试其他后缀

        宏定义共享文件的名字,并且文件的后缀名为".dll",用来迷惑坏人,让他们以为这仅仅是一个动态链接库.当然您也可以使用其他后缀名.

        在WriteReadFileData()中,我们首先通过FileIsExist()来判断文件是否已经存在.

        之后传递具体的参数,实现对文件的创建或是打开.

        这个共享的文件具备这些属性:隐藏|自删除|系统|共享|临时|数据运行期间一直存在.

        我们使用SetFilePointer()在每次读写之时都将读写指针设置为文件头部,用以读写完整的共享数据.

        在GetProcessCountByFileData()中,我们通过WriteReadFileData()读取共享数据.

        然后对数据进行简单解密,并通过遍历数据中存放的进程数组以及myGetProcessPath()实现获取当前程序的启动数量.

        当然还有将本程序的PID加密后写入文件数据的功能.

        这样一个大致的加密存储以及隐藏文件自删除部分代码就完成了.

        实现检测的代码:

        

DWORD CheckCountFileData(DWORD nMaxCount, HANDLE& hFile)
{
	hFile = NULL;
	BOOL isLimit = FALSE;
	ULONG nSize = 0;
	DWORD* pPidArray = NULL;
	__try {

		do
		{
			//通过读取隐藏文件数据,获取当前程序的启动数量
			LONG nCount = GetProcessCountByFileData(hFile);

			//进程数量超出最大数
			if (nCount > nMaxCount)
				isLimit = TRUE;

			//进程数量=0或者-1均表示系统环境异常,此处直接返回TRUE.
			if (nCount <= 0)
				isLimit = TRUE;

		} while (FALSE);

	}
	__except (EXCEPTION_EXECUTE_HANDLER) { ; }


	return isLimit;
}

最后的调用:

int main()
{
    
     //限制多开数量
     LONG nMaxCount = 2;

     //声明hFile句柄变量用来接收文件句柄,在用完之后需要关闭句柄,释放资源.
     HANDLE hFile = NULL;
     //判断进程数量是否超出限制数量
     BOOL isLimit = CheckCountFileData(nMaxCount, hFile);

     if (isLimit)
         MessageBoxA(0, "客户端开启数量超出限制!", "提示", 0);

    system("pause");

    //程序在需要结束的时候,关闭文件句柄
    if (hFile)
        CloseHandle(hFile);
}

实现效果如图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

°默然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值