【问题标题】:Self-deleting program works fine in C but error in C++ project自删除程序在 C 中工作正常,但在 C++ 项目中出错
【发布时间】:2025-12-27 13:05:07
【问题描述】:

我需要一种在我的 Win32 C++ 项目中自删除可执行文件的方法,我找到了一个用 C 语言执行此操作的程序:

selfdel.c:

//  http://www.catch22.net/tuts/win32/self-deleting-executables
//  selfdel.c
//
//  Self deleting executable for Win9x/WinNT (works for all versions of windows)
//
//  J Brown 1/10/2003
//
//  This source file must be compiled with /GZ turned OFF
//  (basically, disable run-time stack checks)
//
//  Under debug build this is always on (MSVC6)
//
//
/**
 * The way this works is:
    * Firstly a child process is created in a suspended state (any process will do - i.e. explorer.exe).
    * Some code is then injected into the address-space of the child process.
    * The injected code waits for the parent-process to exit.
    * The parent-process is then deleted.
    * The injected code then calls ExitProcess, which terminates the child process.
*/
#include <windows.h>
#include <tchar.h>

#pragma pack(push, 1)

#define CODESIZE 0x200

//
//  Structure to inject into remote process. Contains 
//  function pointers and code to execute.
//
typedef struct _SELFDEL
{
    struct _SELFDEL *Arg0;          // pointer to self

    BYTE    opCodes[CODESIZE];      // code 

    HANDLE  hParent;                // parent process handle

    FARPROC fnWaitForSingleObject;
    FARPROC fnCloseHandle;
    FARPROC fnDeleteFile;
    FARPROC fnSleep;
    FARPROC fnExitProcess;
    FARPROC fnRemoveDirectory;
    FARPROC fnGetLastError;

    BOOL    fRemDir;

    TCHAR   szFileName[MAX_PATH];   // file to delete

} SELFDEL;

#pragma pack(pop)

#ifdef _DEBUG
#define FUNC_ADDR(func) (PVOID)(*(DWORD *)((BYTE *)func + 1) + (DWORD)((BYTE *)func + 5))
#else
#define FUNC_ADDR(func) func
#endif

//
//  Routine to execute in remote process. 
//
static void remote_thread(SELFDEL *remote)
{
    // wait for parent process to terminate
    remote->fnWaitForSingleObject(remote->hParent, INFINITE);
    remote->fnCloseHandle(remote->hParent);

    // try to delete the executable file 
    while(!remote->fnDeleteFile(remote->szFileName))
    {
        // failed - try again in one second's time
        remote->fnSleep(1000);
    }

    // finished! exit so that we don't execute garbage code
    remote->fnExitProcess(0);
}

//
//  Delete currently running executable and exit
//  
BOOL SelfDelete(BOOL fRemoveDirectory)
{
    STARTUPINFO         si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    CONTEXT             context;
    DWORD               oldProt;
    SELFDEL             local;
    DWORD               entrypoint;

    TCHAR               szExe[MAX_PATH] = _T("explorer.exe");

    //
    //  Create executable suspended
    //
    if(CreateProcess(0, szExe, 0, 0, 0, CREATE_SUSPENDED|IDLE_PRIORITY_CLASS, 0, 0, &si, &pi))
    {
        local.fnWaitForSingleObject     = (FARPROC)WaitForSingleObject;
        local.fnCloseHandle             = (FARPROC)CloseHandle;
        local.fnDeleteFile              = (FARPROC)DeleteFile;
        local.fnSleep                   = (FARPROC)Sleep;
        local.fnExitProcess             = (FARPROC)ExitProcess;
        local.fnRemoveDirectory         = (FARPROC)RemoveDirectory;
        local.fnGetLastError            = (FARPROC)GetLastError;

        local.fRemDir                   = fRemoveDirectory;

        // Give remote process a copy of our own process handle
        DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), 
            pi.hProcess, &local.hParent, 0, FALSE, 0);

        GetModuleFileName(0, local.szFileName, MAX_PATH);

        // copy in binary code
        memcpy(local.opCodes, FUNC_ADDR(remote_thread), CODESIZE);

        //
        // Allocate some space on process's stack and place
        // our SELFDEL structure there. Then set the instruction pointer 
        // to this location and let the process resume
        //
        context.ContextFlags = CONTEXT_INTEGER|CONTEXT_CONTROL;
        GetThreadContext(pi.hThread, &context);

        // Allocate space on stack (aligned to cache-line boundary)
        entrypoint = (context.Esp - sizeof(SELFDEL)) & ~0x1F;
        
        //
        // Place a pointer to the structure at the bottom-of-stack 
        // this pointer is located in such a way that it becomes 
        // the remote_thread's first argument!!
        //
        local.Arg0 = (SELFDEL *)entrypoint;

        context.Esp = entrypoint - 4;   // create dummy return address
        context.Eip = entrypoint + 4;   // offset of opCodes within structure

        // copy in our code+data at the exe's entry-point
        VirtualProtectEx(pi.hProcess,   (PVOID)entrypoint, sizeof(local), PAGE_EXECUTE_READWRITE, &oldProt);
        WriteProcessMemory(pi.hProcess, (PVOID)entrypoint, &local, sizeof(local), 0);

        FlushInstructionCache(pi.hProcess, (PVOID)entrypoint, sizeof(local));

        SetThreadContext(pi.hThread, &context);

        // Let the process continue
        ResumeThread(pi.hThread);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);

        return TRUE;
    }

    return FALSE;
}

使用 Visual Studio 开发人员命令提示符和 cl 单独编译它会创建一个可执行文件,它会按预期运行。把它放在我的 C++ 项目中会在remote_thread 中产生一个错误:

我无法弄清楚这个错误。在 x64 和 x86 中构建会导致相同的错误。我尝试将它单独放入其中,添加extern "C" {} 作为包装器,将文件作为.c 文件引入,然后与头文件链接,但随后 header 方法破坏了所有其他 Windows 头文件(可能是因为编译器无法区分 c 标头和 cpp 标头?)但除此之外,我在这里遗漏了什么?

【问题讨论】:

  • C 和 C++ 是不同的语言,C++ 有更严格的输入规则。
  • 我相信这是由于 C 将 FARPROC 解释为指向具有未指定数量参数的函数的指针,而 C++ 将相同的定义解释为指向具有 0 个参数的函数的指针。你能告诉我们FARPROC是如何定义的吗? IIRC,类似于typedef int* (*FARPROC)()
  • @Brian 是正确的。尝试给这个文件一个 .c 扩展名(以便编译器将其编译为 C 而不是 C++)。您发布的代码不是有效的 C++ ,因为 SELFDEL 中的函数原型没有指定正确数量和类型的参数。
  • 您可以在您的 C++ 代码中将 SelfDelete 声明为 extern "C" BOOL SelfDelete(BOOL fRemoveDirectory);
  • 你实际上并不需要一个头文件,只需在你的 C++ 文件中,在你实际调用函数之前的某个地方。

标签: c++ c winapi


【解决方案1】:

FARPROC 在 C++ 中的工作方式与在 C 中的工作方式不同。这实际上在 CallWindowProc documentation 中有所描述:

...
FARPROC 类型声明如下:

int (FAR WINAPI * FARPROC) ()

在 C 中,FARPROC 声明表示具有未指定参数列表的回调函数。然而,在 C++ 中,声明中的空参数列表表示函数没有参数。这种细微的区别可能会破坏粗心的代码。
...

因此,您必须改用正确的函数指针签名,例如:

typedef struct _SELFDEL
{
    ...
    DWORD (WINAPI *fnWaitForSingleObject)(HANDLE, DWORD);
    BOOL (WINAPI *fnCloseHandle)(HANDLE);
    BOOL (WINAPI *fnDeleteFile)(LPCTSTR);
    void (WINAPI *fnSleep)(DWORD);
    void (WINAPI *fnExitProcess)(UINT);
    BOOL (WINAPI *fnRemoveDirectory)(LPCTSTR);
    DWORD (WINAPI *fnGetLastError)();
    ...
} SELFDEL;
SELFDEL local;
...
local.fnWaitForSingleObject     = &WaitForSingleObject;
local.fnCloseHandle             = &CloseHandle;
local.fnDeleteFile              = &DeleteFile;
local.fnSleep                   = &Sleep;
local.fnExitProcess             = &ExitProcess;
local.fnRemoveDirectory         = &RemoveDirectory;
local.fnGetLastError            = &GetLastError;
...

【讨论】:

  • “可以破坏粗心的代码” - 很好的发现。官方文档很少这样明确说明无意识的后果。
【解决方案2】:

正如 cmets 中所述,这里的问题是代码在编译为 C++ 时无效,因为函数原型不正确。您可以通过给文件一个.c 后缀来强制编译器将代码编译为C。

您的调用 C++ 代码必须被告知 SelfDelete 是 C 函数而不是 C++ 函数。这是因为 C++ 函数具有 mangled names 以允许诸如重载之类的事情。为此,您可以在 C++ 代码中将函数声明为 extern "C"

extern "C" BOOL SelfDelete(BOOL fRemoveDirectory);

只需将它放在调用 C++ 代码中,在实际调用函数之前的某个位置。没有这个,你的程序将无法链接,因为 C 代码和 C++ 代码会在幕后实际调用的函数上存在分歧。

【讨论】:

    最近更新 更多