返回> 网站首页
[转载]VC下MFC程序自删除(自杀)几种方法的实践与探讨
yoours2011-07-07 17:28:24
简介一边听听音乐,一边写写文章。
在VC下做了个MFC的程序,想让他运行后,自动删除自己。在网上看了些资料,方法也有一些,都实践了一下,感觉对MFC的程序,使用cmd.exe可能更合适一些。其他的方法也蛮好,蛮经典的,不过我感觉用在MFC程序上就不太合适了。
我实践的方法有三种:
1.使用汇编,就是Gary Nebbett的经典代码。
2.使用创建克隆进程方式。
3.使用ShellExecute执行cmd.exe。
第一种方式的代码网上很容易找到,我也在这贴一下。这种方式的劣势就是只能用的Windows 98/NT/2000上,所以XP上就不能考虑了。
#include "windows.h"
int main(int argc, char *argv[])
{
char buf[MAX_PATH];
HMODULE module;
module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);
CloseHandle((HANDLE)4); //Windows 98上不用这行。但要把
//push UnmapViewOfFile换成push FreeLibrary
__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push UnmapViewOfFile
ret
}
return 0;
}
这段代码的原理,理解起来有点拗口,我也把别人的解释贴出来,多看几遍就理解了。
代码的前3排就不说了。从CloseHandle((HANDLE)4)开始讲起。
在网上查找了很多资料查到HANDLE4是OS的硬编码,CloseHandle((HANDLE)4)用于关闭文件语柄。
要删除一个文件必须要删除打开文件的语柄,如果有文件语柄打开将会失败。在上面已经关闭了。
下面重点分析 __asm { } 里面的内容。
经过一连串的PUSH过后。 堆栈里面的内容形成了这样的形式。
以下是在WIN2000 SP3 VC6.0下测试结果。
ESP 栈内的值 栈内地址
0012FE28 0 0012FE28
0012FE24 0 0012FE24
0012FE20 0012FE78 0012FE20 文件全路径
0012FE1C 77E7CF5C 0012FE1C ExitProcess入口
0012FE18 00040000 0012FE18 module的值
0012FE14 77E6E3A6 0012FE14 DeleteFile入口
0012FE10 77E6D2BD 0012FE10 UnmapViewOfFile
接下来就RET我们知道RET是在函数返回的时候调用的。它的功能就是从当前的ESP指向的堆栈中取出函数的返回地址。对于上面的代码来说现在的ESP=0012FE10,现在取出栈地址0012FE10里面的值77E6D2BD,然后跳转到77E6D2BD,这就到了UnmapViewOfFile的函数入口。为什么0012FE10后面是DeleteFile?参数module为什么又到了0012FE18这些以后我们马上解决。
我们先自己编写一个代码
void main ()
{
UnmapViewOfFile(NULL);
}
然后反汇编看看汇编命令是怎么的。如下:
6: UnmapViewOfFile(NULL);
00401028 mov esi,esp
0040102A push 0
0040102C call dword ptr [__imp__UnmapViewOfFile@4 (004241ac)]
00401032 cmp esi,esp
首先是参数0入栈,然后我们追到[__imp__UnmapViewOfFile@4 (004241ac)]
里面去。看看现在的栈是什么样子的。如下:
栈内地址 栈内值
0012FF30 0 参数
0012FF2C 00401032 返回地址
00401032是CALL函数系统帮我们入栈的我们并没有手工添加。但是对于RET我们在转移到UnmapViewOfFile入口的时候并没有一个返回地址的入栈,也就是说push DeleteFile就成了UnmapViewOfFile函数的返回地址。再上面push module才是UnmapViewOfFile的参数。有一点烦琐好好想一想。好的当我们的UnmapViewOfFile函数调用完毕,现在EIP已经到了77E6E3A6,DeleteFile入口。
但是ESP现在在什么位置?应该在0012FE1C栈内的值为77E7CF5C,同样的道理
在DeleteFile返回后程序应该跳转到77E7CF5C也就是ExitProcess的入口。
那么(0012FE1C+4)才是DeleteFile的参数。也就是0012FE78。PUSH EAX。
当我们的DeleteFile返回的时候,程序跳转到了77E7CF5C,ExitProcess的入口。现在的ESP=0012FE24。一样的道理
PUSH 0 这个是ExitProcess的参数
PUSH 0 这个是ExitProcess的返回地址
由于ExitProcess还没有返回进程就结束了 所以ExitProcess的返回地址是0也不会发生内存错误。
现在看第二种方法。代码如下:
if (__argc == 1)
{
HANDLE hFile = NULL; //克隆文件句柄
HANDLE hProcess = NULL; //当前运行进程句柄
TCHAR PathOrig[MAX_PATH] = {0};
TCHAR PathClone[MAX_PATH] = {0};
TCHAR CmdLine[MAX_PATH * 2] = {0}; //参数
//拷贝文件到临时文件中
GetModuleFileName(NULL,PathOrig,MAX_PATH);
GetTempPath(MAX_PATH,PathClone);
GetTempFileName(PathClone,_T("Retri"),0,PathClone);
CopyFile(PathOrig,PathClone,FALSE);
//创建文件运行完毕删除标记
hFile = CreateFile(PathClone,0,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE,NULL);
//信号同步
hProcess = OpenProcess(SYNCHRONIZE,TRUE,GetCurrentProcessId());
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
ZeroMemory(&StartupInfo,sizeof(StartupInfo));
ZeroMemory(&ProcessInfo,sizeof(ProcessInfo));
StartupInfo.cb = sizeof(StartupInfo);
StartupInfo.wShowWindow = SW_HIDE;
wsprintf(CmdLine,_T("%s %d \"%s\""),PathClone,hProcess,PathOrig);
CreateProcess(NULL,CmdLine,NULL,NULL,TRUE,0,NULL,NULL,&StartupInfo,&ProcessInfo);
//关闭句柄
if (hFile)
{
CloseHandle(hFile);
}
if (hProcess)
{
CloseHandle(hProcess);
}
}
else
{
HANDLE hProcess = NULL;
hProcess = (HANDLE)_ttoi(__argv[1]);
//等待前一个进程结束
WaitForSingleObject(hProcess,INFINITE);
if (hProcess)
{
CloseHandle(hProcess);
}
DeleteFile(__argv[2]);
}
这种方法,也蛮好,但不是特别的有效,有时候创建的临时文件不会立马删除,我测试了几次,是这样的。另外一个劣势,就是新创建的进程是原MFC程序进程,所以是有窗口的。这样就会新弹出一个MFC的窗口,当然可以采用某种方式隐藏窗口,但我试了 StartupInfo.wShowWindow = SW_HIDE;不行,加上StartupInfo.dwFlags = STARTF_USESHOWWINDOW;一样也是会弹出窗口。这都是小问题,你终究会找到一种方式来隐藏窗口。但关键问题是第二个进程负责删除原文件,并自动退出。MFC的程序可一般都不是自动退出的哦,都是用户点击叉叉退出的,所以对于MFC程序又必须在初始化时检测状态后自动退出,这就必须要新创建的进程隐藏窗口并自动退出,所以我感觉这种方法适合一些自动化的程序或者没有窗口的程序。
对于MFC的程序,还是那个通用的方法好用,就是使用cmd.exe来删除。代码如下:
//采用批处理
SHELLEXECUTEINFO ExeInfo;
TCHAR ExePath[MAX_PATH] = {0};
TCHAR ParamPath[MAX_PATH] = {0};
TCHAR ComposePath[MAX_PATH] = {0};
GetModuleFileName(NULL,ExePath,MAX_PATH);
GetShortPathName(ExePath,ExePath,MAX_PATH);
GetEnvironmentVariable(_T("COMSPEC"),ComposePath,MAX_PATH);
_tcscpy(ParamPath,_T("/c del "));
_tcscat(ParamPath,ExePath);
_tcscat(ParamPath,_T(" > nul"));
ZeroMemory(&ExeInfo,sizeof(ExeInfo));
ExeInfo.cbSize = sizeof(ExeInfo);
ExeInfo.hwnd = 0;
ExeInfo.lpVerb = _T("Open"); //执行动作,打开
ExeInfo.lpFile = ComposePath; //执行文件全路径名称
ExeInfo.lpParameters = ParamPath; //执行参数
ExeInfo.nShow = SW_HIDE; //执行方式,隐藏窗口。
ExeInfo.fMask = SEE_MASK_NOCLOSEPROCESS; //设置为ShellExecute函数结束后进程退出。
//创建执行命令窗口进程
if (ShellExecuteEx(&ExeInfo))
{
//设置命令行进程级别为空闲基本,这使得本程序有足够的时间退出。
SetPriorityClass(ExeInfo.hProcess,IDLE_PRIORITY_CLASS);
//设置本程序进程基本为实时执行,快速退出。
SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
//通知资源管理器,本程序删除
SHChangeNotify(SHCNE_DELETE,SHCNF_PATH,ExePath,NULL);
}
在程序即将要退出时,执行一个cmd.exe来删除本程序。这种方法我测试了,还是比较好用。
对代码的讲解我就贴一下别人的了。
如果上面代码中的注释还不能帮助你理解代码的意义,请不要着急,后面我们还要对这些代码做详细的讲解。现在,你可以开始编译该程序项目,运行一下程序,请在Windows资源浏览器中仔细观察程序文件,当你按下“开始自杀”按钮后几秒,该程序文件从Windows资源浏览器中消失了,这也正在本程序想要得到的效果。
体验了“自杀”程序的神奇之后,让我们回过头来好好的分析一下实现“自杀”功能的代码。
前面我们已经谈过,实现“自杀”功能的核心是在程序中创建一个命令窗口新进程,通过向命令窗口进程传递del命令和参数来删除程序文件。命令窗口程序是由环境变量COMSPEC定义的,Win9x/ME使用COMMAND.COM,WinNT/2K/XP使用CMD.COM。程序把命令字符串“/c del filename 〉 nul”传递给命令窗口,其中filename是需要删除文件的全路径文件名,文件名需要转换为8.3格式;/c开关用于命令窗口退出。
在实现代码中,首先就需要获取当前程序模块的全路径,并将其转化为命令窗口需要的8.3格式。代码中GetModuleFileName(0,szModule,MAX_PATH)函数实现了获取当前程序模式的全路径名称,并存放到变量szModule中。接着使用GetShortPathName(szModule,szModule,MAX_PATH)函数将szModule变量中的程序模块全路径名称转换成命令窗口需要的8.3格式。另外,还调用GetEnvironmentVariable("COMSPEC",szComspec,MAX_PATH)函数从系统环境变量COMSPC中获取了命令窗口程序的全路径。接下来,需要将存放在变量szModule中的具有8.3格式的程序模块全路径字符串组合成命令字符串“/c del ”+szModule+ “〉 nul”。
有了这些信息之后,就可以调用ShellExecuteEx() API函数创建一个新的命令窗口进程,该函数需要一个SHELLEXECUTEINFO类型的参数,调用ShellExecuteEx()函数必须需要初始化这个类型参数,有关SHELLEXECUTEINFO类型的详细说明请参阅MSDN。本处通过该参数将命令窗口进程的执行动作设为Open、执行文件为命令窗口(路径由szComspec提供)、执行文件参数为上面组合而成的命令字符串、显示方式为隐藏方式(隐藏方式可以阻止出现命令窗口界面)。
命令窗口通过调用ShellExecuteEx()函数以单独的进程运行,它的窗口句柄在SHELLEXECTUEINFO结构中的成员变量hProcess定义。自删除需要解决一个特殊的问题,即主程序必须在命令窗口删除它之前退出并关闭其打开的文件句柄。为了做到这一点,我们必须同步两个独立、并行的进程:当前程序进程和命令窗口进程。这可以通过操作CPU资源优先级来临时降低命令窗口的运行优先级别。这样,主程序将分配到CPU的所有资源直到其正常退出,而阻塞其它任何命令窗口的执行直到主程序结束。下面代码实现调整两个进程的执行优先级:
//设置命令行进程的执行级别为空闲执行,
//这使本程序有足够的时间从内存中退出。
SetPriorityClass(sei.hProcess,IDLE_PRIORITY_CLASS);
//设置本程序进程的执行级别为实时执行,
//这本程序马上获取CPU执行权,快速退出。
SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
到此,自杀”功能基本实现。最后还需要做的事是调用SHChangeNotify(SHCNE_DELETE,SHCNF_PATH,szModule,0) 函数通知Windows资源浏览器已成功删除了程序文件。如果用户当前Windows资源浏览器窗口正处于程序文件目录的话,这个通知是非常必要的,它会导致Windows资源浏览器马上从程序文件目录列表中删除该程序文件项。做完了以上工作,一定要调用退出程序的代码,此处使用了EndDialog()函数,如果不及时退出程序的话,命令窗口进程就不能正常删除程序文件,其原因在前面我们已经研究过。