自删除程序在 C 中运行良好,但在 C++ 项目中出错

Self-deleting program works fine in C but error in C++ project

我需要一种在我的 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 文件引入,然后与头文件链接,但是头方法破坏了所有其他方法windows 头文件(可能是因为编译器无法区分 c 头文件和 cpp 头文件?)但除此之外,我在这里遗漏了什么?

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

中进行了描述

...
The FARPROC type is declared as follows:

int (FAR WINAPI * FARPROC) ()

In C, the FARPROC declaration indicates a callback function that has an unspecified parameter list. In C++, however, the empty parameter list in the declaration indicates that a function has no parameters. This subtle distinction can break careless code.
...

因此,您将不得不使用适当的函数指针签名,例如:

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;
...

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

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

extern "C" BOOL SelfDelete(BOOL fRemoveDirectory);

只需将其放入您的调用 C++ 代码中,在您实际调用该函数之前的某处。没有这个,你的程序将不会 link,因为 C 代码和 C++ 代码将不同意在幕后实际调用的函数。