在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\res目录中,因此SourceExe或ExeHelper重新编译后,都需重新编译ExePackage,重新将SourceExe.exe和ExeHelper.exe打包进ExePackage.exe。

运行的效果图如下:

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

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

应用程序启动后修改自身EXE文件或自删除EXE文件(附VC++6.0源码)的更多相关文章

  1. Yii2.0源码分析之——控制器文件分析(Controller.php)创建动作、执行动作

    在Yii中,当请求一个Url的时候,首先在application中获取request信息,然后由request通过urlManager解析出route,再在Module中根据route来创建contr ...

  2. Qt5 UI信号、槽自动连接的控件重名大坑(UI生成的槽函数存在一个隐患,即控件重名。对很复杂的控件,不要在 designer 里做提升,而是等到程序启动后,再动态创建,可以避免很多问题)

    对Qt5稍有熟悉的童鞋都知道信号.槽的自动连接机制.该机制使得qt designer 设计的UI中包含的控件,可以不通过显式connect,直接和cpp中的相应槽相关联.该机制的详细文章见 http: ...

  3. 代码实现程序启动后, 可以从键盘输入接收多个整数, 直到输入quit时结束输入. 把所有输入的整数倒序排列打印

    package com.loaderman.test; import java.util.Comparator; import java.util.Scanner; import java.util. ...

  4. 简单的股票信息查询系统 1 程序启动后,给用户提供查询接口,允许用户重复查股票行情信息(用到循环) 2 允许用户通过模糊查询股票名,比如输入“啤酒”, 就把所有股票名称中包含“啤酒”的信息打印出来 3 允许按股票价格、涨跌幅、换手率这几列来筛选信息, 比如输入“价格>50”则把价格大于50的股票都打印,输入“市盈率<50“,则把市盈率小于50的股票都打印,不用判断等于。

    '''需求:1 程序启动后,给用户提供查询接口,允许用户重复查股票行情信息(用到循环)2 允许用户通过模糊查询股票名,比如输入“啤酒”, 就把所有股票名称中包含“啤酒”的信息打印出来3 允许按股票价格 ...

  5. Solr4.8.0源码分析(9)之Lucene的索引文件(2)

    Solr4.8.0源码分析(9)之Lucene的索引文件(2) 一. Segments_N文件 一个索引对应一个目录,索引文件都存放在目录里面.Solr的索引文件存放在Solr/Home下的core/ ...

  6. Solr4.8.0源码分析(12)之Lucene的索引文件(5)

    Solr4.8.0源码分析(12)之Lucene的索引文件(5) 1. 存储域数据文件(.fdt和.fdx) Solr4.8.0里面使用的fdt和fdx的格式是lucene4.1的.为了提升压缩比,S ...

  7. Solr4.8.0源码分析(11)之Lucene的索引文件(4)

    Solr4.8.0源码分析(11)之Lucene的索引文件(4) 1. .dvd和.dvm文件 .dvm是存放了DocValue域的元数据,比如DocValue偏移量. .dvd则存放了DocValu ...

  8. Solr4.8.0源码分析(10)之Lucene的索引文件(3)

    Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这 ...

  9. Solr4.8.0源码分析(8)之Lucene的索引文件(1)

    Solr4.8.0源码分析(8)之Lucene的索引文件(1) 题记:最近有幸看到觉先大神的Lucene的博客,感觉自己之前学习的以及工作的太为肤浅,所以决定先跟随觉先大神的博客学习下Lucene的原 ...

随机推荐

  1. Oracle中的二进制、八进制、十进制、十六进制相互转换函数

    原文:Oracle中的二进制.八进制.十进制.十六进制相互转换函数 Oracle中的二进制.八进制.十进制.十六进制相互转换函数   今天在网上看到一篇关于在oracle中对各种进制数进行转换的帖子, ...

  2. HTML5它contenteditable属性

    HTML5它contenteditable属性 1.功能说明 (1)功能:同意用户编辑元素中的内容 (2)说明:是一个布尔值.false是不能编辑,true为可编辑 2.分析实例 (1)content ...

  3. JS自动化测试 单元测试之Qunit

    前言 因为公司开发了一套javascript SDK需要测试,在网上找了很久,找到了JQuery团队开发的QUnit,和基于JUnit的JsUnit,还有一些还没有看,先讲讲QUnit吧 下载 登录J ...

  4. 亮点面试题&amp;&amp;实现Singleton(辛格尔顿)模式-JAVA版本

    称号:设计一个类.我们只能产生这个类的一个实例.(来自<剑指Offer>) 解析:仅仅能生产一个实例的类是实现Singleton(单例)模式的类型.因为设计模式在面向对象程序设计中起着举足 ...

  5. C语言生成2000w行数据

    最近一直抽空学习shell,脚本语言看多了多多少少有些蛋疼不适,所以捡起以前遇到的一个C语言的问题看看. 原先应该是在C++吧关注的一个帖子,楼主为了测试数据库性能需要如下形式的数据要求: 字符串长度 ...

  6. Web学习-apache视图log刊物

    视图apache刊物 apache日志位置 不同的系统位置不同. widnows 假如是windows的话,xampp下应该是都存在的,直接去找apache的folder/log/access.log ...

  7. C#编程实践--字符串反转

    朴素反转 朴素解法,倒序遍历,字符串拼接,字符串性能低下,在长度已知的前提可以使用char数组代替 public static string NaiveReverse(string text) { s ...

  8. 使用javaservice 将jboss 注册为服务

    近来做项目,需要jboss定期重新启动.不想再看到jboss启动那个黑洞洞的窗口,就想着把它注册为服务,然后在net start.恰好objectweb上有个open source的javaservi ...

  9. Nhibernate分页测试续

    Nhibernate分页测试续(附源码) 接着上一篇Nhibernate分页测试,最近一直在接触Nhibernate,接触的越多.了解越深,越是感觉他的强大,很多功能都封装的很好,对数据操作是那么的简 ...

  10. Android 5.0之应用中实现材料设计—Material Design

    上午的时候在刷Google+,看到了Abraham Williams转发了一篇强文,是Android Developers网站新发的一篇博客—Implementing Material Design ...