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

2023-11-16

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

VC下MFC程序自删除(自杀)几种方法的实践与探讨 的相关文章

  • SQL Server Express(或任何版本)可以在 RPi 上运行吗?

    我注意到完整版的 Windows 10 可以在 RPI 3 上运行 我想知道 SQL Server Express 或任何其他版本 是否可用于 ARM 版本的 Windows 我在任何地方都看不到它 所以我怀疑答案是否定的 但想检查一下我是
  • 删除指针后将其设为 NULL 是一个好习惯吗?

    我首先要说的是 使用智能指针 您将永远不必担心这个问题 下面的代码有什么问题 Foo p new Foo use p delete p p NULL 这是由答案和评论 https stackoverflow com questions 19
  • 是否可以调用驻留在 exe 中的非导出函数?

    我想调用驻留在第 3 方 exe 中的函数并获取其结果 好像有should是一种方法 只要我知道函数地址 调用约定等 但我不知道如何 有谁知道我会怎么做 我意识到任何解决方案都是非标准的黑客 但有must成为一种方式 我的非恶意用例 我正在
  • 避免 Inno Setup 中的“无法展开 shell 文件夹常量 userdocs”错误

    我将一些示例文档安装到 Windows 上标准 我的文档 文件夹的 PerfectTablePlan 子文件夹中 这对于 99 以上的用户来说效果很好 但是 如果用户没有 我的文档 文件夹 我会收到许多以下形式的丑陋错误消息 内部错误 无法
  • 为什么我们从 MultiByte 转换为 WideChar?

    我习惯于处理 ASCII 字符串 但现在使用 UNICODE 我对一些术语感到非常困惑 什么是多字节字符以及什么是widechar有什么不同 多字节是指在内存中包含多个字节的字符吗 widechar只是一个数据类型来表示吗 为什么我们要从M
  • 在 Jenkins 中执行批处理文件

    我有一个简单的批处理文件 我想要从 Jenkins 调用 运行 执行该文件 Jenkins 中有同样的插件吗 如何从 Jenkins 执行批处理文件 如果有相同的教程或文档 无需为此添加新插件 在Jenkins 选择您的工作名称并转到配置部
  • 为什么我的文件路径中出现 Unicode 转义的语法错误? [复制]

    这个问题在这里已经有答案了 我想要访问的文件夹名为 python 位于我的桌面上 当我尝试访问它时出现以下错误 gt gt gt os chdir C Users expoperialed Desktop Python SyntaxErro
  • 如何通过 DOS 批处理命令发送电子邮件?

    我在 DOS 中有一个批处理文件 可以进行一些检查 完成后我需要发送一封电子邮件 我在 interwebz 上找到了一些解决方案 但大多数都是第三方的 或者只是在 Outlook 中打开新邮件 我需要命令来发送完整的电子邮件 而无需任何人工
  • 调试器忽略动态加载的 DLL 中的错误

    我有一个与自编码 DLL 的调试相关的非常奇怪的问题 我有一个 MFC 驱动的基于对话框的应用程序 几个静态链接的项目和几个在运行时加载的 DLL 项目 我在调试中构建解决方案 运行应用程序 然后我可以轻松调试这些 DLL 项目 现在问题来
  • 在 Windows 2008 上将 myprogram.exe 作为服务运行时出现问题

    MyProgram exe 是用来侦听来自管道的请求并使用命令提示符使其工作完美 但我尝试使用 Windows 服务来工作但没有成功我在 Windows Server 2008 Enterprise 上尝试了以下步骤 gt sc creat
  • 在 SVG 路径中动态创建渐变层

    我正在使用 SVG 创建动态路径 我现在希望在我的路径中添加渐变 但我被困住了 按照我尝试的方式 我的渐变沿着图 2 所示的路径进行 而我要求它是图 1 中的那种 Current 我的渐变和描边定义如下
  • 使用 IDLE 编辑的 .py 文件消失了

    我曾经有过Edit with IDLE当我右键单击时的选项 py文件 但我多次卸载 重新安装以使某些东西正常工作 但现在它消失了 我检查了注册表HKEY CLASSES ROOT and HKEY LOCAL MACHINE对于价值低于Py
  • 导入错误:无法导入名称线程

    这是我第一次学习Python 我继续尝试线程这篇博文 http www saltycrane com blog 2008 09 simplistic python thread example 问题是它似乎已经过时了 import time
  • 如何更改选项卡控件的名称

    我在 C WinForms 应用程序中使用选项卡控件 我想更改选项卡的标题 默认情况下它们是 tabPage1 tabPage2 等 一种无需代码即可实现的懒惰方法 选择选项卡控件 Go to properties use F4 to do
  • 如何在 C++ 中急于提交分配的内存?

    总体情况 带宽 CPU 使用率和 GPU 使用率都极其密集的应用程序需要每秒从一个 GPU 向另一个 GPU 传输约 10 15GB 的数据 它使用 DX11 API 来访问 GPU 因此上传到 GPU 只能在每次上传都需要映射的缓冲区中进
  • 确定用于映射网络驱动器的域和用户名

    使用带有 SP1 的 Windows 7 Enterprise 但我希望得到适用于 Windows XP 2003 2008 Vista 7 的通用答案 从命令提示符处 我执行net use命令将 Z 驱动器映射到另一台计算机上的共享 但我
  • 用于验证 IIS 设置的 Powershell 脚本

    是否可以使用 Power Shell 脚本获取 IIS 设置 我希望使用脚本获取 检查以下信息 检查 Windows 身份验证提供程序是否正确列出 协商 NTLM 检查是否启用了 Windows 身份验证 Windows 身份验证高级设置
  • 通过 __get() 通过引用返回 null

    快速规格 PHP 5 3 error reporting 1 the highest 我正在使用 get 通过引用技巧神奇地访问对象中任意深度的数组元素 快速示例 public function get key return isset t
  • 设置 Form.KeyPreview = true 的缺点?

    我想知道 Form KeyPreview 属性实际上有什么用处 它为什么存在以及将其设置为 true 会带来什么 风险 我想它一定有some负面影响 否则它根本不应该存在 或者至少默认情况下是正确的 EDIT 我很清楚what确实如此 我问
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win

随机推荐