带有 Shell / CreateProcess() 的 C++ 自我更新程序

问题描述

我想用 C++ 做一个自我更新程序。我看过这篇关于执行后如何删除自己的帖子。

How to write a program in C++ such that it will delete itself after execution?

我希望我的程序在执行后运行这个 powershell 代码Image

然后运行 ​​newFile.exe。

我已经尝试让它工作但没有运气。如果我执行 CreateProcess() 是否需要延迟 szCmd?

提前致谢:)

编辑:

我几乎按照我想要的方式得到它,代码

void shellMove(std::string source,std::string target) {
STARTUPINFOA si = { 0 };
PROCESS_informatION pi = { 0 };

std::string str = "powershell.exe Move-Item -Path " + source + " -Destination " + target + " -Force; " + target;
LPSTR s = const_cast<char*>(str.c_str());

CreateProcessA(NULL,s,NULL,FALSE,CREATE_NEW_CONSOLE,&si,&pi);

CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}

std::string getDirPath() {
char buffer[MAX_PATH] = { 0 };
GetmodulefileNameA(NULL,buffer,MAX_PATH);
return std::string(buffer).substr(0,std::string(buffer).find_last_of("\\"));
}

std::string getFilePath() {
char buffer[MAX_PATH] = { 0 };
GetmodulefileNameA(NULL,MAX_PATH);
return std::string(buffer);
}

void main() {

std::string fileName = "Test.exe";

shellMove(getDirPath() + "\\" + fileName + "~",getDirPath() + "\\" + fileName);
}

问题是,Test.exe 是一个 C++ 控制台应用程序,这意味着如果我这样做:

std::string str = "powershell.exe Move-Item -Path " + source + " -Destination " + target 
+ " -Force; " + target;

+目标;它使用 Powershell 启动控制台应用程序:/ 我希望它以普通控制台启动,如何执行此操作?请帮忙谢谢:)

解决方法

可以在不创建外部进程的情况下删除self exe,这将等待我们的进程终止。

来自Jonas L的想法

ULONG DeleteSelfWin32()
{
    // MAX_PATH for more simply code here
    WCHAR path[MAX_PATH];

    GetModuleFileNameW(0,path,_countof(path));

    ULONG dwError = GetLastError();
    
    if (dwError == NOERROR)
    {
        HANDLE hFile = CreateFileW(path,DELETE,FILE_SHARE_DELETE,OPEN_EXISTING,0);

        if (hFile != INVALID_HANDLE_VALUE)
        {
            FILE_RENAME_INFO fri = { TRUE,2 * sizeof(WCHAR),':' };
            fri.FileName[1] = '~'; // exist place in FILE_RENAME_INFORMATION due align on HANDLE
            dwError = SetFileInformationByHandle(hFile,FileRenameInfo,&fri,sizeof(fri)) ? NOERROR : GetLastError();

            CloseHandle(hFile);

            if (dwError == NOERROR)
            {
                hFile = CreateFileW(path,0);

                if (hFile != INVALID_HANDLE_VALUE)
                {
                    FILE_DISPOSITION_INFO fdi = { TRUE };
                    dwError = SetFileInformationByHandle(hFile,FileDispositionInfo,&fdi,sizeof(fdi)) ? NOERROR : GetLastError();
                    CloseHandle(hFile);
                }
                else
                {
                    dwError = GetLastError();
                }
            }
        }
        else
        {
            dwError = GetLastError();
        }
    }

    return dwError;
}

或此代码的 NT 版本

NTSTATUS DeleteSelfNT()
{
    // MAX_PATH for more simply code here
    WCHAR path[MAX_PATH];

    GetModuleFileNameW(0,_countof(path));

    if (GetLastError()) return STATUS_NAME_TOO_LONG;

    NTSTATUS status;
    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES oa = { sizeof(oa),&ObjectName };

    if (0 <= (status = RtlDosPathNameToNtPathName_U_WithStatus(path,&ObjectName,0)))
    {
        HANDLE hFile;
        IO_STATUS_BLOCK iosb;

        if (0 <= NtOpenFile(&hFile,&oa,&iosb,0))
        {
            FILE_RENAME_INFORMATION fri = { true,2*sizeof(WCHAR),':'};
            fri.FileName[1] = '~'; // exist place in FILE_RENAME_INFORMATION due align on HANDLE
            status = NtSetInformationFile(hFile,sizeof(fri),FileRenameInformation);
            NtClose(hFile);

            if (0 <= status)
            {
                if (0 <= (status = NtOpenFile(&hFile,0)))
                {
                    FILE_DISPOSITION_INFORMATION fdi = { true };
                    status = NtSetInformationFile(hFile,sizeof(fdi),FileDispositionInformation);
                    NtClose(hFile);
                }
            }
        }

        RtlFreeUnicodeString(&ObjectName);
    }

    return status;
}

在 win 7、8.1、10 上测试和工作。但认为这是 ntfs 实现中的错误。

文件真的被删除了,而不是简单地变得不可见。此后的父文件夹也可以删除(如果其中没有更多文件)。通过调用 NtQueryVolumeInformationFileFileFsFullSizeInformation 检查 - AvailableAllocationUnits 实际上增加了文件大小和 FSCTL_GET_NTFS_FILE_RECORD(文件 ID 是从 NtQueryInformationFile 获得的 FileInternalInformation ) - 以这种方式删除文件后记录变得无效。如果我们将 exe 复制回来 - SequenceNumber 增加(如果相同的 MftRecordIndex 重用)

如果不想基于错误而删除购买创建子进程,例如可能的下一个解决方案:

EXTERN_C
NTSYSAPI
VOID
NTAPI
RtlDispatchAPC(
               PAPCFUNC pfnAPC,ULONG_PTR dwData,PVOID ApcActivationContext
               );

BOOL DeleteSelf()
{
    WCHAR path[MAX_PATH];
    GetModuleFileNameW(0,_countof(path));

    if (GetLastError()) return FALSE;

    BOOL fOk = FALSE;

    struct OA_UN : public OBJECT_ATTRIBUTES,UNICODE_STRING {} oa {sizeof(OBJECT_ATTRIBUTES)};

    if (0 <= RtlDosPathNameToNtPathName_U_WithStatus(path,0))
    {
        HANDLE hFile;
        IO_STATUS_BLOCK iosb;

        oa.ObjectName = &oa;
        NTSTATUS status = NtOpenFile(&hFile,0);
        RtlFreeUnicodeString(&oa);

        if (0 <= status)
        {
            if (GetEnvironmentVariableW(L"ComSpec",_countof(path)))
            {
                STARTUPINFOW si = { sizeof(si) };
                PROCESS_INFORMATION pi;
                if (CreateProcessW(path,const_cast<PWSTR>(L"* /C exit\r\n"),CREATE_SUSPENDED|DETACHED_PROCESS,&si,&pi))
                {
                    HANDLE hProcess;
                    if (DuplicateHandle(NtCurrentProcess(),NtCurrentProcess(),pi.hProcess,&hProcess,SYNCHRONIZE,FALSE,0) &&
                        0 <= ZwQueueApcThread(pi.hThread,(PKNORMAL_ROUTINE)ZwWaitForSingleObject,hProcess,0))
                    {
                        fOk = DuplicateHandle(NtCurrentProcess(),hFile,&oa.RootDirectory,DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE);

                        hFile = 0;

                        if (fOk)
                        {
                            oa.ObjectName = 0;
                            PVOID pv;

                            fOk = ( pv = VirtualAllocEx(pi.hProcess,sizeof(oa),MEM_COMMIT,PAGE_READWRITE)) &&
                                (oa.ObjectName = reinterpret_cast<OA_UN*>(pv)) &&
                                WriteProcessMemory(pi.hProcess,pv,0) && 
                                0 <= ZwQueueApcThread(pi.hThread,(PKNORMAL_ROUTINE)RtlDispatchAPC,ZwDeleteFile,INVALID_HANDLE_VALUE);
                                
                        }
                    }
                    ResumeThread(pi.hThread);
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);
                }
            }

            if (hFile) NtClose(hFile);
        }
    }

    return fOk;
}

这里我们执行 cmd.exe (%ComSpec%) 命令 do exit - " /C exit\r\n"*命令行(当我们直接传递应用程序名称时 - 系统不需要解析和修改命令行,它可以是常量字符串)。并向 cmd 注入 2 个 APC 调用 - 首先等待我们的进程退出,然后删除文件