• 应用程序启动后修改自身EXE文件或自删除EXE文件(附VC++6.0源码)


    在CSDN论坛看到这么一个问题:如何为第三方工具加上使用限制次数?问题的答案很简单,重新做一个应用程序,将第三方程序打包进这个应用程序,启动应用程序时可以检查第三方工具的使用次数,检查通过,可运行第三方工具。至于如果检查使用次数,答案也是五花八门,写注册表、写配置文件、读取硬盘、网卡信息等,都是常规做法。我也思考了下,还可以这样:

    也是重写应用程序,但将使用次数写入到应用程序文件里面,每使用一次,就更新一下应用程序文件里的计数。

    这种方案想来有几个问题:1.如果运行应用程序前做了备份,则每当使用次数完后,还可以用备份应用程序运行;2.我们知道,一个应用程序运行后,是不能修改自身EXE文件的。因此这个方案也只是想想而已,并不实用。但是要实现这种方案还是需要一定的技术,我闲来无事,就写了一个DEMO。

    每2个问题,如何在应用程序运行时候修改自身EXE文件?答案也很简单,应用程序运行时加载另一个应用程序,自己退出,让另一个应用程序来修改EXE文件。既然应用程序运行时可以修改本身EXE文件,那么删除自身的EXE文件也是可以的,因此这种方法还可以用于卸载软件中,卸载软件时,可以使用此方法删除卸载软件本身。

    好了,我们把目标明确一下:

    1.第三方软件为SourceExe.exe,为了演示,我们还是自己模拟第三方,自己写;

    2.加载的应用程序为ExePackage.exe;

    3.修改ExePackage.exe的程序为ExeHelper.exe。

    SourceExe.exe和ExeHelper.exe以资源形式加载进ExePackage.exe。启动ExePackage.exe时,ExePackage.exe并不显示界面,只将SourceExe.exe和ExeHelper.exe释放到临时目录,然后启动ExeHelper.exe,并告诉ExeHelper.exe一些必要的信息(DEMO中通过参数传递)。

    ExeHelper.exe启动时,首先等待ExePackage.exe结束,这样才能修改ExePackage.exe文件。然后从ExePackage.exe文件末尾读取16个字节,测试是否匹配我们指定的数据(以区别这些数据是EXE文件本身的数据,还是我们添加的数据),这16个字节的最后4个字节保存的是使用次数,这样我们就可得到使用次数了,使用一次后,再将数据更新到EXE文件,最后就可以启动SourceExe.exe了。

    原理很简单,但用到的技术还是不少的:

    1.子进程等待父进程结束再处理事务(应用程序的自删除);

    2.父进程将句柄等信息传递给子进程;

    3.将EXE中加载的资源释放为本地文件。

    下面就开始上代码,代码很简单,就不一一解释了,只在需要说明的地方说明一下:

    首先是SourceExe.exe的源码,作为第三方工具,代码很简单,就不多说了。

    SourceExe.cpp:

    // SourceExe.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <iostream>
    #include <conio.h>
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
    	cout << "Hello world!" << endl;
    	getch();
    	return 0;
    }
    


    然后是ExeHelper.exe的源码,仔细看,也不太难。

    ExeHelper.cpp:

    // ExeHelper.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <iostream>
    #include <conio.h>
    #include <windows.h>
    
    using namespace std;
    
    BYTE g_byCompare[16] = {'1', '2', '0', '6', '8', '0', '4', '5', '1', 0, 0, 0, 0, 0, 0, 0};
    DWORD dwDefaultTimes = 30;
    
    int main(int argc, char* argv[])
    {
    	if (NULL == argv[0] || 0 == strlen(argv[0])
    		|| NULL == argv[1] || 0 == strlen(argv[1])
    		|| NULL == argv[2] || 0 == strlen(argv[2])
    		|| NULL == argv[3] || 0 == strlen(argv[3]))
    	{
    		cout << "argv error!" << endl;
    		getch();
    		return 0;
    	}
    	cout << "argv[0]=" << argv[0] << endl;
    	cout << "argv[1]=" << argv[1] << endl;
    	cout << "argv[2]=" << argv[2] << endl;
    	cout << "argv[3]=" << argv[3] << endl;
    	
    	// 保证父进程已退出,才能操作父进程的EXE文件
    	HANDLE handle=(HANDLE)(atoi(argv[1]));
        if (WAIT_FAILED == WaitForSingleObject(handle, INFINITE))
    	{
    		cout << "WaitForSingleObject Error (" << GetLastError() << ")" << endl;
    		getch();
    		return 0;
    	}
    	
    	HANDLE hFile = CreateFile(
    		argv[2], 
    		GENERIC_READ | GENERIC_WRITE,
    		0,
    		NULL,
    		OPEN_EXISTING,
    		0,
    		NULL);
    	BYTE pBuffer[16] = {0};
    	DWORD dwFileSizeLow = 0;
    	DWORD dwFileSizeHigh = 0;
    	dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
    	if (dwFileSizeLow < 16)
    	{
    		cout << "The size of " << argv[2] << " is " << dwFileSizeLow
    			 << ", less than 16 bytes." << endl;
    		getch();
    		return 0;
    	}
    	
    	SetFilePointer(hFile, -16, NULL, FILE_END);
    	DWORD dwBytesRead = 0;
    	ReadFile(hFile, pBuffer, 16, &dwBytesRead, NULL);
    	
    	DWORD dwTimes = 0;
    	memcpy(&dwTimes, pBuffer + 12, sizeof(DWORD));
    
    	// pBuffer的前12个字节应该与g_byCompare的前12个字节相同,后4个字节为使用次数
    	if (0 != memcmp(pBuffer, g_byCompare, 12))
    	{
    		// pBuffer前12个字节不是g_byCompare的前12个字节,则认为是第一次启动
    		// 在EXE文件末尾添加上默认的30次使用次数
    		dwTimes = dwDefaultTimes;
    		memcpy(pBuffer, g_byCompare, 16);
    		memcpy(pBuffer + 12, &dwTimes, sizeof(DWORD));
    		SetFilePointer(hFile, 0, NULL, FILE_END);
    		DWORD dwBytesWrite = 0;
    		WriteFile(hFile, pBuffer, 16, &dwBytesWrite, NULL);
    	}
    	
    	cout << "Can use " << dwTimes << " times" << endl;
    	if (0 == dwTimes)
    	{
    		getch();
    		return 0;
    	}
    	cout << "Press any key to run" << endl;
    	getch();
    
    	// 减少使用次数,并将使用次数再更新到EXE文件
    	dwTimes--;
    	memcpy(pBuffer + 12, &dwTimes, sizeof(DWORD));
    	SetFilePointer(hFile, -16, NULL, FILE_END);
    	DWORD dwBytesWrite = 0;
    	WriteFile(hFile, pBuffer, 16, &dwBytesWrite, NULL);
    	CloseHandle(hFile);
    	
    	STARTUPINFO si;
    	memset(&si, 0, sizeof(si));
    	si.cb = sizeof(si);
    	PROCESS_INFORMATION pi;
    	memset(&pi, 0, sizeof(pi));
    	
    	// 然后启动第三方EXE
    	CreateProcess(
    		argv[3],
    		NULL,
    		NULL,
    		NULL,
    		FALSE,
    		NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
    		NULL,
    		NULL,
    		&si,
    		&pi);
    	CloseHandle(pi.hThread);
    	CloseHandle(pi.hProcess);
    	
    	return 0;
    }
    


    最后是ExePackage.exe的源码,ExePackage.cpp:

    CExePackageApp theApp;
    
    /////////////////////////////////////////////////////////////////////////////
    // CExePackageApp initialization
    
    /*  
    函数功能:释放资源文件  
           
    参数说明:DWORD   dwResID			:指定要释放的资源ID号,如IDR_EXE  
         LPCTSTR lpszResType		:指定释放的资源的资源类型  
         LPCTSTR lpszFilePathName	:指定释放后的目标文件名  
           
    返回值:成功则返回TRUE,失败返回FALSE  
    
    使用方法:BOOL bRet = FreeResFile(IDR_SOURCE_EXE, _T("EXE"), _T("D:\123.txt"));
    		  其中,IDR_SOURCE_EXE为EXE类型的资源ID
    */
    BOOL FreeResFile(DWORD dwResID, LPCTSTR lpszResType, LPCTSTR lpszFilePathName)  
    {     
        HMODULE hInstance = ::GetModuleHandle(NULL);//得到自身实例句柄  
               
        HRSRC   hResID = ::FindResource(hInstance, MAKEINTRESOURCE(dwResID), lpszResType); //查找资源
        HGLOBAL hRes   = ::LoadResource(hInstance, hResID); //加载资源  
        LPVOID  pRes   = ::LockResource(hRes); //锁定资源  
               
        if (NULL == pRes) //锁定失败  
        {  
            return FALSE;  
        }  
        DWORD  dwResSize = ::SizeofResource(hInstance, hResID); //得到待释放资源文件大小  
        HANDLE hResFile  = CreateFile(
    		lpszFilePathName,
    		GENERIC_WRITE,
    		0,
    		NULL,
    		CREATE_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL);   
        if (INVALID_HANDLE_VALUE == hResFile)  
        {  
            return FALSE;  
        }  
           
        DWORD dwWritten = 0; //写入文件的大小     
        WriteFile(hResFile, pRes, dwResSize, &dwWritten, NULL); //写入文件  
        CloseHandle(hResFile); //关闭文件句柄
    	
    	FreeResource(hRes);
               
        return (dwResSize == dwWritten); //若写入大小等于文件大小,返回成功,否则失败  
    }
    
    BOOL CExePackageApp::InitInstance()
    {
    	AfxEnableControlContainer();
    
    	// Standard initialization
    	// If you are not using these features and wish to reduce the size
    	//  of your final executable, you should remove from the following
    	//  the specific initialization routines you do not need.
    
    #ifdef _AFXDLL
    	Enable3dControls();			// Call this when using MFC in a shared DLL
    #else
    	Enable3dControlsStatic();	// Call this when linking to MFC statically
    #endif
    
    	TCHAR szTmpDir[MAX_PATH] = {0};
    	GetTempPath(MAX_PATH, szTmpDir);
    	
    	TCHAR szSourceExeFilePathName[MAX_PATH] = {0};
    	_stprintf(szSourceExeFilePathName, _T("%sSourceExe.exe"), szTmpDir);
    	
    	TCHAR szExeHelperFilePathName[MAX_PATH] = {0};
    	_stprintf(szExeHelperFilePathName, _T("%sExeHelper.exe"), szTmpDir);
    	
    	DeleteFile(szSourceExeFilePathName);
    	DeleteFile(szExeHelperFilePathName);
    	FreeResFile(IDR_SOURCE_EXE, "EXE", szSourceExeFilePathName);
    	FreeResFile(IDR_EXE_HELPER, "EXE", szExeHelperFilePathName);
    	
    	TCHAR szModuleName[MAX_PATH] = {0};
    	GetModuleFileName(NULL, szModuleName, MAX_PATH);
    	
    	STARTUPINFO si;
    	memset(&si, 0, sizeof(si));
    	si.cb = sizeof(si);
    	PROCESS_INFORMATION pi;
    	memset(&pi, 0, sizeof(pi));
    	
        SECURITY_ATTRIBUTES sa;  
    	memset(&sa, 0, sizeof(sa));
        sa.nLength = sizeof(sa);  
        sa.lpSecurityDescriptor = NULL;  
        sa.bInheritHandle = TRUE; 
    	
    	// 得到此进程的句柄,传递给ExeHelper.exe,因此这儿不能关闭hParentProcess
    	// 需要在子进程中关闭
        HANDLE hParentProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId());
        TCHAR szCommandLine[MAX_PATH * 3] = {0};
    	// 注意此处的参数,调用CreateProcess时,并不会将EXE的文件名也当作参数
    	// 因此,需要在参数列表中加上EXE的文件名(ExeHelper.exe并不使用)
        _stprintf(szCommandLine, _T(""%s" %d "%s" "%s""),
    		szExeHelperFilePathName, (DWORD)hParentProcess, szModuleName, szSourceExeFilePathName);
    	
        CreateProcess(
    		szExeHelperFilePathName,
    		szCommandLine,
    		&sa,
    		NULL,
    		TRUE,
    		NULL,
    		NULL,
    		NULL,
    		&si,
    		&pi);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    
    // 	CExePackageDlg dlg;
    // 	m_pMainWnd = &dlg;
    // 	int nResponse = dlg.DoModal();
    // 	if (nResponse == IDOK)
    // 	{
    // 		// TODO: Place code here to handle when the dialog is
    // 		//  dismissed with OK
    // 	}
    // 	else if (nResponse == IDCANCEL)
    // 	{
    // 		// TODO: Place code here to handle when the dialog is
    // 		//  dismissed with Cancel
    // 	}
    
    	// Since the dialog has been closed, return FALSE so that we exit the
    	//  application, rather than start the application's message pump.
    	return FALSE;
    }


    需要说明:

    创建的是MFC对话框程序,因为方便导入SourceExe.exe和ExeHelper.exe作为资源。SourceExe.exe和ExeHelper.exe都是导入的EXE资源。ID可见代码或下图。代码中使用了FreeResFile函数来释放资源到本地文件。注释掉了启动对话框的代码,只将SourceExe.exe和ExeHelper.exe释放到临时目录,运行ExeHelper.exe后立即退出。SourceExe和ExeHelper编译后,会自动将生成的EXE复制到ExePackage es目录中,因此SourceExe或ExeHelper重新编译后,都需重新编译ExePackage,重新将SourceExe.exe和ExeHelper.exe打包进ExePackage.exe。


    运行的效果图如下:


    当然,你也可做成非Console的界面,获取参数就需使用GetCommandLine()。

    源码下载地址:应用程序启动后修改自身EXE文件或自删除EXE文件




  • 相关阅读:
    PHP trim() 函数
    php 计算2个日期的相差天数
    php date('Y-n-j')的和date('Y-m-d')的区别
    转移服务器
    Invalid argument supplied for foreach()解决办法
    wordpress 后台忘记密码怎么办
    qrcode js插件生成微信二维码
    thinkphp5 注释
    tp5 新增完数据,获取id
    resstFul服务文件上传下载
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3170462.html
Copyright © 2020-2023  润新知