应用程序启动后修改自身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:

 
 
  
  
  1. // SourceExe.cpp : Defines the entry point for the console application.
  2. //
  3.  
  4. #include "stdafx.h"
  5. #include <iostream>
  6. #include <conio.h>
  7.  
  8. using namespace std;
  9.  
  10. int main(int argc, char* argv[])
  11. {
  12. cout << "Hello world!" << endl;
  13. getch();
  14. return 0;
  15. }

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

  ExeHelper.cpp:

 
 
  
  
  1. // ExeHelper.cpp : Defines the entry point for the console application.
  2. //
  3.  
  4. #include "stdafx.h"
  5. #include <iostream>
  6. #include <conio.h>
  7. #include <windows.h>
  8.  
  9. using namespace std;
  10.  
  11. BYTE g_byCompare[16] = { '1', '2', '0', '6', '8', '0', '4', '5', '1', 0, 0, 0, 0, 0, 0, 0};
  12. DWORD dwDefaultTimes = 30;
  13.  
  14. int main(int argc, char* argv[])
  15. {
  16. if (NULL == argv[0] || 0 == strlen(argv[0])
  17. || NULL == argv[1] || 0 == strlen(argv[1])
  18. || NULL == argv[2] || 0 == strlen(argv[2])
  19. || NULL == argv[3] || 0 == strlen(argv[3]))
  20. {
  21. cout << "argv error!" << endl;
  22. getch();
  23. return 0;
  24. }
  25. cout << "argv[0]=" << argv[0] << endl;
  26. cout << "argv[1]=" << argv[1] << endl;
  27. cout << "argv[2]=" << argv[2] << endl;
  28. cout << "argv[3]=" << argv[3] << endl;
  29. // 保证父进程已退出,才能操作父进程的EXE文件
  30. HANDLE handle=(HANDLE)(atoi(argv[1]));
  31. if (WAIT_FAILED == WaitForSingleObject(handle, INFINITE))
  32. {
  33. cout << "WaitForSingleObject Error (" << GetLastError() << ")" << endl;
  34. getch();
  35. return 0;
  36. }
  37. HANDLE hFile = CreateFile(
  38. argv[2],
  39. GENERIC_READ | GENERIC_WRITE,
  40. 0,
  41. NULL,
  42. OPEN_EXISTING,
  43. 0,
  44. NULL);
  45. BYTE pBuffer[16] = { 0};
  46. DWORD dwFileSizeLow = 0;
  47. DWORD dwFileSizeHigh = 0;
  48. dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
  49. if (dwFileSizeLow < 16)
  50. {
  51. cout << "The size of " << argv[2] << " is " << dwFileSizeLow
  52. << ", less than 16 bytes." << endl;
  53. getch();
  54. return 0;
  55. }
  56. SetFilePointer(hFile, -16, NULL, FILE_END);
  57. DWORD dwBytesRead = 0;
  58. ReadFile(hFile, pBuffer, 16, &dwBytesRead, NULL);
  59. DWORD dwTimes = 0;
  60. memcpy(&dwTimes, pBuffer + 12, sizeof(DWORD));
  61.  
  62. // pBuffer的前12个字节应该与g_byCompare的前12个字节相同,后4个字节为使用次数
  63. if (0 != memcmp(pBuffer, g_byCompare, 12))
  64. {
  65. // pBuffer前12个字节不是g_byCompare的前12个字节,则认为是第一次启动
  66. // 在EXE文件末尾添加上默认的30次使用次数
  67. dwTimes = dwDefaultTimes;
  68. memcpy(pBuffer, g_byCompare, 16);
  69. memcpy(pBuffer + 12, &dwTimes, sizeof(DWORD));
  70. SetFilePointer(hFile, 0, NULL, FILE_END);
  71. DWORD dwBytesWrite = 0;
  72. WriteFile(hFile, pBuffer, 16, &dwBytesWrite, NULL);
  73. }
  74. cout << "Can use " << dwTimes << " times" << endl;
  75. if (0 == dwTimes)
  76. {
  77. getch();
  78. return 0;
  79. }
  80. cout << "Press any key to run" << endl;
  81. getch();
  82.  
  83. // 减少使用次数,并将使用次数再更新到EXE文件
  84. dwTimes--;
  85. memcpy(pBuffer + 12, &dwTimes, sizeof(DWORD));
  86. SetFilePointer(hFile, -16, NULL, FILE_END);
  87. DWORD dwBytesWrite = 0;
  88. WriteFile(hFile, pBuffer, 16, &dwBytesWrite, NULL);
  89. CloseHandle(hFile);
  90. STARTUPINFO si;
  91. memset(&si, 0, sizeof(si));
  92. si.cb = sizeof(si);
  93. PROCESS_INFORMATION pi;
  94. memset(&pi, 0, sizeof(pi));
  95. // 然后启动第三方EXE
  96. CreateProcess(
  97. argv[3],
  98. NULL,
  99. NULL,
  100. NULL,
  101. FALSE,
  102. NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
  103. NULL,
  104. NULL,
  105. &si,
  106. &pi);
  107. CloseHandle(pi.hThread);
  108. CloseHandle(pi.hProcess);
  109. return 0;
  110. }

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

 
 
  
  
  1. CExePackageApp theApp;
  2.  
  3. /
  4. // CExePackageApp initialization
  5.  
  6. /*
  7. 函数功能:释放资源文件
  8. 参数说明:DWORD dwResID :指定要释放的资源ID号,如IDR_EXE
  9.      LPCTSTR lpszResType :指定释放的资源的资源类型
  10.      LPCTSTR lpszFilePathName :指定释放后的目标文件名
  11. 返回值:成功则返回TRUE,失败返回FALSE
  12.  
  13. 使用方法:BOOL bRet = FreeResFile(IDR_SOURCE_EXE, _T("EXE"), _T("D:\\123.txt"));
  14. 其中,IDR_SOURCE_EXE为EXE类型的资源ID
  15. */
  16. BOOL FreeResFile(DWORD dwResID, LPCTSTR lpszResType, LPCTSTR lpszFilePathName)
  17. {
  18. HMODULE hInstance = ::GetModuleHandle(NULL);//得到自身实例句柄
  19. HRSRC hResID = ::FindResource(hInstance, MAKEINTRESOURCE(dwResID), lpszResType); //查找资源
  20. HGLOBAL hRes = ::LoadResource(hInstance, hResID); //加载资源
  21. LPVOID pRes = ::LockResource(hRes); //锁定资源
  22. if (NULL == pRes) //锁定失败
  23. {
  24. return FALSE;
  25. }
  26. DWORD dwResSize = ::SizeofResource(hInstance, hResID); //得到待释放资源文件大小
  27. HANDLE hResFile = CreateFile(
  28. lpszFilePathName,
  29. GENERIC_WRITE,
  30. 0,
  31. NULL,
  32. CREATE_ALWAYS,
  33. FILE_ATTRIBUTE_NORMAL,
  34. NULL);
  35. if (INVALID_HANDLE_VALUE == hResFile)
  36. {
  37. return FALSE;
  38. }
  39. DWORD dwWritten = 0; //写入文件的大小
  40. WriteFile(hResFile, pRes, dwResSize, &dwWritten, NULL); //写入文件
  41. CloseHandle(hResFile); //关闭文件句柄
  42. FreeResource(hRes);
  43. return (dwResSize == dwWritten); //若写入大小等于文件大小,返回成功,否则失败
  44. }
  45.  
  46. BOOL CExePackageApp::InitInstance()
  47. {
  48. AfxEnableControlContainer();
  49.  
  50. // Standard initialization
  51. // If you are not using these features and wish to reduce the size
  52. // of your final executable, you should remove from the following
  53. // the specific initialization routines you do not need.
  54.  
  55. #ifdef _AFXDLL
  56. Enable3dControls(); // Call this when using MFC in a shared DLL
  57. #else
  58. Enable3dControlsStatic(); // Call this when linking to MFC statically
  59. #endif
  60.  
  61. TCHAR szTmpDir[MAX_PATH] = { 0};
  62. GetTempPath(MAX_PATH, szTmpDir);
  63. TCHAR szSourceExeFilePathName[MAX_PATH] = { 0};
  64. _stprintf(szSourceExeFilePathName, _T("%sSourceExe.exe"), szTmpDir);
  65. TCHAR szExeHelperFilePathName[MAX_PATH] = { 0};
  66. _stprintf(szExeHelperFilePathName, _T("%sExeHelper.exe"), szTmpDir);
  67. DeleteFile(szSourceExeFilePathName);
  68. DeleteFile(szExeHelperFilePathName);
  69. FreeResFile(IDR_SOURCE_EXE, "EXE", szSourceExeFilePathName);
  70. FreeResFile(IDR_EXE_HELPER, "EXE", szExeHelperFilePathName);
  71. TCHAR szModuleName[MAX_PATH] = { 0};
  72. GetModuleFileName(NULL, szModuleName, MAX_PATH);
  73. STARTUPINFO si;
  74. memset(&si, 0, sizeof(si));
  75. si.cb = sizeof(si);
  76. PROCESS_INFORMATION pi;
  77. memset(&pi, 0, sizeof(pi));
  78. SECURITY_ATTRIBUTES sa;
  79. memset(&sa, 0, sizeof(sa));
  80. sa.nLength = sizeof(sa);
  81. sa.lpSecurityDescriptor = NULL;
  82. sa.bInheritHandle = TRUE;
  83. // 得到此进程的句柄,传递给ExeHelper.exe,因此这儿不能关闭hParentProcess
  84. // 需要在子进程中关闭
  85. HANDLE hParentProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId());
  86. TCHAR szCommandLine[MAX_PATH * 3] = { 0};
  87. // 注意此处的参数,调用CreateProcess时,并不会将EXE的文件名也当作参数
  88. // 因此,需要在参数列表中加上EXE的文件名(ExeHelper.exe并不使用)
  89. _stprintf(szCommandLine, _T("\"%s\" %d \"%s\" \"%s\""),
  90. szExeHelperFilePathName, (DWORD)hParentProcess, szModuleName, szSourceExeFilePathName);
  91. CreateProcess(
  92. szExeHelperFilePathName,
  93. szCommandLine,
  94. &sa,
  95. NULL,
  96. TRUE,
  97. NULL,
  98. NULL,
  99. NULL,
  100. &si,
  101. &pi);
  102. CloseHandle(pi.hProcess);
  103. CloseHandle(pi.hThread);
  104.  
  105. // CExePackageDlg dlg;
  106. // m_pMainWnd = &dlg;
  107. // int nResponse = dlg.DoModal();
  108. // if (nResponse == IDOK)
  109. // {
  110. // // TODO: Place code here to handle when the dialog is
  111. // // dismissed with OK
  112. // }
  113. // else if (nResponse == IDCANCEL)
  114. // {
  115. // // TODO: Place code here to handle when the dialog is
  116. // // dismissed with Cancel
  117. // }
  118.  
  119. // Since the dialog has been closed, return FALSE so that we exit the
  120. // application, rather than start the application's message pump.
  121. return FALSE;
  122. }

  需要说明:

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


  运行的效果图如下:


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

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


猜你喜欢

转载自blog.csdn.net/guo0625/article/details/10099213
今日推荐