返回> 网站首页 

[转载]VC下MFC程序自删除(自杀)几种方法的实践与探讨

yoours2011-07-07 17:28:24 阅读 1464

简介一边听听音乐,一边写写文章。

在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()函数,如果不及时退出程序的话,命令窗口进程就不能正常删除程序文件,其原因在前面我们已经研究过。  

微信小程序扫码登陆

文章评论

1464人参与,0条评论