文件自删除的一些资料与实现

来源:互联网 发布:workbanch 导出mysql 编辑:程序博客网 时间:2024/05/20 01:45

原文链接

https://blogs.msdn.microsoft.com/oldnewthing/20160108-00/?p=92821

MSDN 上关于 FILE_FLAG_DELETE_ON_CLOSE 的描述

       当文件的所有句柄都被关闭的时候,文件立马被删除,包括这个函数返回的句柄以及其它的打开的句柄以及复制的句柄。
       如果已经存在了这个文件的一个打开的句柄,这个函数调用失败,除非所有的打开都指定了FILE_SHARE_DELTE 参数。
       之后如果打开文件时不指定FILE_SHARE_DELTE 标志,将无法打开文件。

MSDN 上关于 FILE_FLAG_DELETE_ON_CLOSE 的讨论

       客户有一些和FILE_FLAG_DELTE_ON_CLOSE 标志有关的问题。“创建一个文件,当我们使用完成的时候,自动清除该文件。无论何时我们打开该文件的时候,请求GENERIC_READ|GENERIC_WRITE 标志,并允许所有的访问模式(FILE_SHARE_READ | FILE_SHARE_WRITE|FILE_SHARE_DELETE),并传入了FILE_FLAG_DELETE_ON_CLOSE 标志。我们可以用这种方式打开文件很多次,但是一旦我们调用了CloseHandle 函数关闭了其中的任何一个打开的句柄,将再也无法打开新的句柄。”
        FILE_FLAG_DELETE_ON_CLOSE 意味着在指向文件对象的最后一个句柄被删除之后,文件将被删除。这和关闭文件对应的最后一个句柄并不是同一个概念,每一次调用CreateFile 都将创建一个新的文件对象。可以通过调用DuplicateHandle 函数来创建指向同一个文件对象的多个句柄。
        当指向文件对象的最后一个句柄被关闭之后,文件对象删除底层的文件。已经存在的文件对象将继续引用该文件,但是不允许再创建新文件。当没有新的文件对象的时候,对应的目录项被移除。
        回到上面的问题,用户可以通过调用DuplicateHandle 而不是CreateFile 来得到额外的文件句柄,因为所有的句柄都引用了同样的文件对象,直到所有的句柄都被关闭,否则文件不会被删除。

一篇介绍自删除的英文文章

https://www.catch22.net/tuts/self-deleting-executables

        作者说了,除了最后一种全都是别人已经研究过的了,其中Jeffrey Richter 1996 年(21年前)已经发表过这方面的文章了。

为什么下面的方式不行

       WinXP 之前是存在自删除程序的。但是如果在新版本的系统上执行下面的代码什么都不会发生。

TCHAR szFilePath[_MAX_PATH];// Get current executable pathGetModuleFileName(NULL, szFilePath, _MAX_PATH);// Delete specified fileDeleteFile(szFilePath);

       当前的Windows 操作系统使用内存映射文件将可执行文件加载到系统中执行。当进程执行的时候,磁盘文件一直是打开的,当且仅当进程退出的时候才会关闭该文件(文件句柄)。
       比较直观的一个例子是,我们通过explorer 删除一个正在执行的进程的文件的话,explorer 将弹出警告提示框。

MoveFileEx 函数

       这种方法是将文件删除延迟到系统重新引导的时候。这个函数告诉系统将一个文件从一个地方移动到另一个地方,当第二个参数为NULL,即目标为NULL 的时候,就是删除文件的操作了。通常情况下,如果我们第一个参数传入的是当前可执行文件的完整路径,第二个参数为NULL 的话,函数将失败,但是,如果第三个参数传入的是MOVEFILE_DELAY_UNTIL_REBOOT 标志,这就高速系统直到系统关机或者重新引导的时候再进行移动(或者删除)操作。

MoveFileEx(szExistingFile, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);

       这里有几个问题。首先,无法删除重定向文件或者目录,第二,无法立即删除文件。

通过WININIT.INI 删除文件

       Win95/98/ME 操作系统下,系统引导的时候将执行WININIT.EXE 程序,该程序查找WININIT.INI 文件。如果文件存在,系统查找一个称为[Rename]的节,其下面的每一项都对应一个重命名操作,该操作将在系统重启的时候被执行。类似于MoveFileEx 函数。

[Rename]NUL=c:\dir\myapp.exec:\dir\new.exe=c:\dir\old.exe

       第一个行代表一个重启删除操作,第二行代表一个重命名操作。

自删除批处理方法

       这是一个广为人知的方法。当我们执行下面的这个批处理文件的时候

del %0.bat

       可能会收到提示说本文件不存在,但是当我们把后面的路径改为本批处理文件的完整路径的时候,将成功实现自删除。如果程序想要实现自删除,可以通过生成一个下面所示的批处理文件并执行即可。

:Repeatdel "C:\MYDIR\MYPROG.EXE"if exist "MYPROG.EXE" goto Repeatrmdir "C:\MYDIR"del "\DelUS.bat"

       可以通过降低新运行的批处理程序的优先级来使其在我们的程序运行之后再开始执行自删除操作。

COMSPEC 方法

BOOL SelfDelete(){  TCHAR szFile[MAX_PATH], szCmd[MAX_PATH];  if((GetModuleFileName(0,szFile,MAX_PATH)!=0) &&     (GetShortPathName(szFile,szFile,MAX_PATH)!=0))  {    lstrcpy(szCmd,"/c del ");    lstrcat(szCmd,szFile);    lstrcat(szCmd," >> NUL");    if((GetEnvironmentVariable("ComSpec",szFile,MAX_PATH)!=0) &&       ((INT)ShellExecute(0,0,szFile,szCmd,0,SW_HIDE)>32))       return TRUE;  }  return FALSE;}

       只要COMSPEC 环境变量存在,这个方法就可以使用。默认情况下它总是存在的,而且是操作系统的命令解释器的完整路径。
       另外我们需要注意的是,我们新建一个隐藏窗口的shell 进程并执行下面的命令:

del exepath >> NUL

       这个命令将删除我们的可执行文件,但是当且仅当我们的进程已经退出,这个命令才能执行成功,因此我们应该设置新生成的shell 进程一个相比我们的进程比较低的优先级,或者提升自己的优先级,以确保此命令的执行时机比我们的进程退出的时机晚。

DELETE_ON_CLOSE 方法

       这种方法理论上比较好理解,就是尝试着创建一个在关闭时删除的文件,特殊的地方在于,原来的进程在创建该文件并以DELETE_ON_CLOSE 打开该文件的句柄之后,又以这个文件创建一个新的进程,用这个新创建的进程来删除原来的文件,并利用这个关闭时删除的特殊性质实现在进程退出的时候自动删除进程文件。但是通过测试,这个方法并不成功。

/**************************************************DeleteMe.CPP  Module name: DeleteMe.cpp Written by: Jeffrey Richter Description: Allows an EXEcutable file to delete itself **************************************************/#include <Windows.h>#include <stdlib.h>#include <tchar.h>#include <stdio.h>int main(int argc,char* argv[]) {    if (argc == 1) {        // Original EXE: Spawn clone EXE to delete this EXE        // Copy this EXEcutable image into the user's temp directory                 CHAR szPathOrig[_MAX_PATH], szPathClone[_MAX_PATH];        GetModuleFileName(NULL, szPathOrig, _MAX_PATH);        GetTempPath(_MAX_PATH, szPathClone);        GetTempFileName(szPathClone, ("Del"), 0, szPathClone);        CopyFile(szPathOrig, szPathClone, FALSE);        // Open the clone EXE using FILE_FLAG_DELETE_ON_CLOSE        SECURITY_ATTRIBUTES file_si = { 0 };        file_si.bInheritHandle = TRUE;        file_si.nLength = sizeof(SECURITY_ATTRIBUTES);        HANDLE hfile = CreateFileA(szPathClone, 0, FILE_SHARE_READ | FILE_SHARE_DELETE, &file_si, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);        if (hfile != INVALID_HANDLE_VALUE)        {            printf("创建文件%s 成功\r\n", szPathClone);        }        TCHAR szCmdLine[512];        HANDLE hProcessOrig = OpenProcess(SYNCHRONIZE, TRUE, GetCurrentProcessId());        sprintf(szCmdLine, ("%s %d \"%s\""), szPathClone, hProcessOrig, szPathOrig);        STARTUPINFO si;        ZeroMemory(&si, sizeof(si));        si.cb = sizeof(si);        PROCESS_INFORMATION pi;        if (CreateProcessA(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {            printf("创建新进程成功\r\n");        }        CloseHandle(hProcessOrig);        CloseHandle(hfile);        // This original process can now terminate.    }    else {        // Clone EXE: When original EXE terminates, delete it        getchar();        HANDLE hProcessOrig = (HANDLE)_ttoi(argv[1]);        WaitForSingleObject(hProcessOrig, INFINITE);        CloseHandle(hProcessOrig);        DeleteFile(argv[2]);        // Insert code here to remove the subdirectory too (if desired).                 // The system will delete the clone EXE automatically        // because it was opened with FILE_FLAG_DELETE_ON_CLOSE    }    return(0);}

Win9X 使用的自删除方法

       下面的这种方法据说已经测试过了。简单来说就是利用了函数调用的流程,在栈上面构造一个特别的调用堆栈,然后利用一个ret 指令删除文件并退出进程,关键在于删除文件之后的代码不可以访问之前可执行文件被映射到的地址范围。

#include <windows.h>int main(int argc, char *argv[]){    char    buf[MAX_PATH];    HMODULE module;    module = GetModuleHandle(0);    GetModuleFileName(module, buf, MAX_PATH);    __asm     {      lea     eax, buf      push    0      push    0      push    eax      push    ExitProcess      push    module      push    DeleteFile      push    FreeLibrary      ret    }    return 0;}

       有个疑问就是,为什么这个程序在新版本的系统上无法执行。调试发现DeleteFile 函数拒绝访问。
这里写图片描述

XP+ 解决方案

       这种方法利用系统进程RunDLL32 来执行一个DLL,该DLL 在我们的目标进程退出的时候将进程文件删除,之后DLL 自删除即可。因为DLL 的使用方法和EXE 不同,因此该DLL 可以进行自删除

extern "C" __declspec(dllexport) void CALLBACK SelfDel(HWND, HINSTANCE, LPTSTR lpCmdLine, int)  {      DWORD dwProcessId = 0;      DWORD dwFlagIndex = lstrlen(lpCmdLine);      while(lpCmdLine[dwFlagIndex] != '+')          dwFlagIndex--;      for(int i = dwFlagIndex + 1; i < lstrlen(lpCmdLine); i++)      {          dwProcessId = dwProcessId * 10 + (lpCmdLine[i] - (int)'0');      }      lpCmdLine[dwFlagIndex] = '/0';      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);      WaitForSingleObject(hProcess, INFINITE);      CloseHandle(hProcess);      DeleteFile(lpCmdLine);      char szFileName[MAX_PATH];      GetModuleFileName(g_hModDll, szFileName, sizeof(szFileName));      __asm      {          lea eax, szFileName          push 0          push 0          push eax          push ExitProcess          push g_hModDll          push DeleteFile          push FreeLibrary          ret      }      // 对于DLL 来说,上面的实现是可以进行的}  

       下面是一种使用远程线程注入来实现自删除的例子。其实跟下面的劫持已经存在的已知进程的思路是一样的,让已经存在的计算机固有的程序来执行我们的自删除代码。

#include <windows.h>#include <Tlhelp32.h>#include <iostream>#include <iomanip>#include <assert.h>using namespace std;#pragma comment(lib, "Psapi.lib")typedef BOOL(__stdcall* PFN_DeleteFile)(LPCTSTR);typedef DWORD(__stdcall* PFN_WaitForSingleObject)(HANDLE, DWORD);typedef HANDLE(__stdcall* PFN_OpenProcess)(DWORD, BOOL, DWORD);typedef BOOL(__stdcall* PFN_CloseHandle)(HANDLE);typedef int(WINAPI *pfnMessageBox)(__in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption,  __in UINT uType); typedef struct tagRmtData{    CHAR    m_szData[MAX_PATH];   // path of file to be deleted.    HANDLE   m_processHandle;    void*    m_pfnDeleteFile;    void*    m_pfnWaitForSingleObject;    void*    m_pfnOpenProcess;    void*    m_pfnCloseHandle;    void*    m_pfnMessageBoxA;} RMTDATA, *PRMTDATA;// Function for the remote thread.// 不能让编译器对这个函数做优化,或者任何涉及到本进程的其它内存位置的访问// 缓冲区安全检查为FALSE,直接编写这个函数是有缺陷的,得好好设置编译器的选项 /*__declspec(naked) 该类型不允许返回值,设置VS 使得没有引用本进程变量/函数即可*/DWORD WINAPI RmtFunc(LPVOID lpParam){    PRMTDATA    pData = (PRMTDATA)lpParam;    PFN_WaitForSingleObject pfnWaitForSingleObject =        (PFN_WaitForSingleObject)pData->m_pfnWaitForSingleObject;    pfnWaitForSingleObject(pData->m_processHandle, INFINITE);    pfnMessageBox   pfnMessageBoxA = (pfnMessageBox)pData->m_pfnMessageBoxA;    PFN_CloseHandle pfnCloseHandle = (PFN_CloseHandle)pData->m_pfnCloseHandle;    pfnCloseHandle(pData->m_processHandle);    // pfnMessageBoxA(NULL, pData->m_szData, NULL, MB_OK);    PFN_DeleteFile pfnDeleteFile = (PFN_DeleteFile)pData->m_pfnDeleteFile;    pfnDeleteFile(pData->m_szData);    return 0;}// Endmark is used to get size of RpcFunc. Incremental Linking must be disabledvoid __stdcall Endmark() {}// 按理说应该通过其它的方式来得到对方进程中对应函数的地址BOOL FillRPCData(PRMTDATA pData){    CHAR szFile[MAX_PATH];    GetModuleFileNameA(0, szFile, MAX_PATH);    lstrcpy(pData->m_szData, szFile);    HMODULE hInst = ::LoadLibrary("kernel32.dll");    if (hInst != NULL)    {        pData->m_pfnDeleteFile = GetProcAddress(hInst, "DeleteFileA");        pData->m_pfnWaitForSingleObject = GetProcAddress(hInst, "WaitForSingleObject");        pData->m_pfnOpenProcess = GetProcAddress(hInst, "OpenProcess");        pData->m_pfnCloseHandle = GetProcAddress(hInst, "CloseHandle");        pData->m_pfnMessageBoxA = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");        FreeLibrary(hInst);    }    if (!pData->m_pfnDeleteFile || !pData->m_pfnWaitForSingleObject || !pData->m_pfnOpenProcess || !pData->m_pfnCloseHandle)        return FALSE;    else        return TRUE;}// 得到进程对应的用户名,令牌->SID->用户名BOOL ProcessUserName(DWORD dwProcessID, TCHAR* szUserName){    BOOL   bSuccess = FALSE;    HANDLE hProcess = NULL;    HANDLE hToken = NULL;    TOKEN_USER *pTokenUser = NULL;    __try    {        hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessID);        if (NULL == hProcess)            __leave;        if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))        {            __leave;        }        DWORD dwNeedLen = 0;        GetTokenInformation(hToken, TokenUser, NULL, 0L, &dwNeedLen);        if (dwNeedLen == 0)        {            __leave;        }        pTokenUser = (TOKEN_USER*)new BYTE[dwNeedLen];        if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwNeedLen, &dwNeedLen))        {            __leave;        }        SID_NAME_USE sn;        DWORD dwDmLen = MAX_PATH;        DWORD dwNameLen = MAX_PATH;        TCHAR szDomainName[MAX_PATH];        if (!LookupAccountSid(NULL, pTokenUser->User.Sid, szUserName, &dwNameLen, szDomainName, &dwDmLen, &sn))        {            __leave;        }        bSuccess = TRUE;    }    __finally    {        if (hProcess != NULL)            CloseHandle(hProcess);        if (hToken != NULL)            CloseHandle(hToken);        if (pTokenUser)            delete[](BYTE*)pTokenUser;    }    return bSuccess;}BOOL EnableDebugPrivilege(){    HANDLE hToken;    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))    {        return FALSE;    }    LUID Luid;    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid))    {        return FALSE;    }    TOKEN_PRIVILEGES tp;    tp.PrivilegeCount = 1;    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;    tp.Privileges[0].Luid = Luid;    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))    {        return FALSE;    }    return TRUE;}// Convert the last error code to description string.LPTSTR ConvertErrorCodeToString(DWORD dwErrorCode){    HLOCAL hLocalAddress = NULL;    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,        NULL, dwErrorCode, 0, (LPTSTR)&hLocalAddress, 0, NULL);    return (LPTSTR)hLocalAddress;}BOOL NewRemoteThreadForDelete(DWORD dwProcessID, TCHAR* szProcessName){    BOOL   bRmtSuccess = FALSE;    HANDLE hRmtProcess = NULL;    LPVOID pRmtFuncAddr = NULL;    LPVOID pRmtDataAddr = NULL;    HANDLE hRmtThread = NULL;    RMTDATA stRmtData = { 0 };    DWORD  dwRmtFunSize = 0x1000;// (DWORD)Endmark - (DWORD)RmtFunc;    __try    {        if (!FillRPCData(&stRmtData))            __leave;        hRmtProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);        if (!hRmtProcess)            __leave;        if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(),            hRmtProcess, &stRmtData.m_processHandle, PROCESS_ALL_ACCESS, FALSE, 0))        {            return FALSE;        }        printf("句柄值:%d\r\n", stRmtData.m_processHandle);        LPVOID pRmtFuncAddr = VirtualAllocEx(hRmtProcess, NULL, dwRmtFunSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);        if (!pRmtFuncAddr)            __leave;        PULONG_PTR  FuncAddress =(PULONG_PTR) RmtFunc;        ULONG_PTR TrueAddress   = (ULONG_PTR)FuncAddress + *(PDWORD)((ULONG_PTR)FuncAddress + 1) + 5;        if (!WriteProcessMemory(hRmtProcess, pRmtFuncAddr,(LPCVOID) TrueAddress, dwRmtFunSize, NULL))            __leave;        LPVOID pRmtDataAddr = VirtualAllocEx(hRmtProcess, NULL, sizeof(RMTDATA), MEM_COMMIT, PAGE_READWRITE);        if (!pRmtDataAddr)            __leave;        if (!WriteProcessMemory(hRmtProcess, pRmtDataAddr, &stRmtData, sizeof(RMTDATA), 0))            __leave;        printf("线程:%x\t内存%x\r\n", pRmtFuncAddr, pRmtDataAddr);        DWORD   dwThreadId = 0;        hRmtThread = CreateRemoteThread(hRmtProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRmtFuncAddr, pRmtDataAddr, 0, &dwThreadId);        if (!hRmtThread)        {            // We should never get here, print out some error message if this happens.            // The exe will still be able to deleted if a target process can be found.            LPTSTR lpStr = ConvertErrorCodeToString(GetLastError());            cout << left << setw(25) << "Exception: " << "CreateRemoteThread" << endl                << setw(25) << "Host Process Name:" << szProcessName << endl                << setw(25) << "Last Error Description" << lpStr << endl << endl;            __leave;        }        printf("线程ID \t%d\r\n", dwThreadId);        bRmtSuccess = TRUE;    }    __finally    {        if (hRmtProcess != NULL)        {            if (pRmtFuncAddr != NULL)                VirtualFreeEx(hRmtProcess, pRmtFuncAddr, dwRmtFunSize, MEM_RELEASE);            if (pRmtDataAddr != NULL)                VirtualFreeEx(hRmtProcess, pRmtDataAddr, sizeof(RMTDATA), MEM_RELEASE);            CloseHandle(hRmtProcess);        }        if (hRmtThread != NULL)            CloseHandle(hRmtThread);    }    return bRmtSuccess;}// // 通过句柄得到PE 文件 然后判断 IMAGE_OPTIONAL_HEADER.Magic.// 或者使用IsWow64Process 函数也可以达到同样的目的// 这里使用第二种方法,更简单快捷BOOL IsSameBitSize(DWORD dwProcessID, TCHAR* szProcessName){    BOOL bSameBitSize = FALSE;    HANDLE hProcess = NULL;    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessID);    if (hProcess == NULL)    {        assert(FALSE);    }    typedef        BOOL        (WINAPI            *pfnIsWow64Process)(                __in  HANDLE hProcess,                __out PBOOL Wow64Process                );    pfnIsWow64Process fnIsWow64Process = (pfnIsWow64Process)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process");    if (!fnIsWow64Process)    {        return TRUE;    }    BOOL bSelf, bTarget;    if (fnIsWow64Process(GetCurrentProcess(), &bSelf) && fnIsWow64Process(hProcess, &bTarget))    {        return bSelf == bTarget;    }    else    {        return FALSE;    }}BOOL FindTargetProcess(DWORD& dwProcessID, TCHAR* szProcessName){    EnableDebugPrivilege();    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    if (hProcessSnap == INVALID_HANDLE_VALUE)        return FALSE;    PROCESSENTRY32 procEntry = { 0 };    procEntry.dwSize = sizeof(PROCESSENTRY32);    if (Process32First(hProcessSnap, &procEntry))    {        // 得到当前用户名        DWORD dwCurUserNameLen = MAX_PATH;        TCHAR szCurUserName[MAX_PATH] = { 0 };        if (!GetUserName(szCurUserName, &dwCurUserNameLen))            return FALSE;        do        {   // 得到当前进程对应的用户名            TCHAR szUserName[MAX_PATH] = { 0 };            if (!ProcessUserName(procEntry.th32ProcessID, szUserName))                continue;            // 不是当前进程,用户名相同的时候才能使用CreateRemoteThread 进行注入。            // 另外,我们需要目标进程与我们的指令大小相同。            if (procEntry.th32ProcessID != GetCurrentProcessId() &&                lstrcmp(szCurUserName, szUserName) == 0 &&                IsSameBitSize(procEntry.th32ProcessID, procEntry.szExeFile))            {                dwProcessID = procEntry.th32ProcessID;                lstrcpy(szProcessName, procEntry.szExeFile);                CloseHandle(hProcessSnap);                return TRUE;            }        } while (Process32Next(hProcessSnap, &procEntry));    }    CloseHandle(hProcessSnap);    return FALSE;}BOOL SelfDel_5(){    DWORD dwProcessID = 0;    TCHAR szProcessName[MAX_PATH] = { 0 };    if (FindTargetProcess(dwProcessID, szProcessName))    {        cout << "Host Process Found: " << szProcessName << endl;        if (NewRemoteThreadForDelete(dwProcessID, szProcessName))        {               cout << "Create Remote Thread Success!" << endl << szProcessName <<endl;            getchar();            return TRUE;        }        cout << "Create Remote Thread Fail." << endl;    }    return FALSE;}int main(){    SelfDel_5();    //RmtFunc(0);// 为了测试生成的函数代码是EIP/RIP 相关且进程无关的,即符合ShellCode 代码的要求    return 0;}

最终版本的文件自删除方法

       执行流程如下:
1. 创建一个暂停状态的子进程
2. 注入一些代码到子进程的进程空间中。
3. 被注入的代码等待当前进程退出。
4. 父进程被删除。
5. 子进程调用ExitProcess 函数以退出进程。

       这个方法的巧妙之处在于,子进程是任意的,另一方面由于创建的时候子进程设置为暂停,因此不会出现任何窗口,子进程执行删除父进程文件的代码之后直接调用ExitProcess 退出进程了,不会有其它额外的影响。
       之前的版本的方法由于使用CreateRemoteThread 函数在暂停状态的子进程中创建一个线程,其只能运行于NT 系统,后来将方法转变为一种劫持线程的方法。即在暂停的进程中申请我们自己的堆栈空间,然后将代码写入,并将EIP/RIP 修改为被注入的代码的地址,恢复线程执行,线程执行想要的功能之后直接调用ExitProcess 退出进程。
       这个方法涉及到两个问题,首先就是线程劫持的方法。第二个就是当我们改变被暂停的进程的主线程的RIP/EIP 的时候,由于该进程尚未进行任何初始化工作,因此一些函数的调用可能不成功,比如所MessageBox 函数等。
       如何进行适当的操作,以在这个未完全初始化的进程中执行任意我们想执行的代码?
http://blogorama.nerdworks.in/selfdeletingexecutables/
       上面的链接给出了答案,我们要将我们的代码替换原来的进程的入口点,而不是新构建一个入口点然后再修改RIP。此时,当程序执行到我们的代码的时候肯定已经执行完了初始化工作。代码的实施需要我们了解PE 文件的结构,以找到程序的入口地址。
       这个方法的好处在于,不留痕迹,远程线程的自删除实现最多只能做到删除注入所需要的代码,但是做这一步还是需要修改栈属性为可读写执行,然后在栈中构建代码并清理内存。但是,栈的属性无法恢复,多少留下痕迹。因此这种方法更加不留痕迹。

#include <Windows.h>#include <stdio.h>#include <tchar.h>typedef PVOID *PPVOID;typedef LONG KPRIORITY;typedef struct _CLIENT_ID {    DWORD                   ClientID0;    DWORD                   ClientID1; // thread id} CLIENT_ID, *PCLIENT_ID;typedef struct _PEB {    BOOLEAN                 InheritedAddressSpace;    BOOLEAN                 ReadImageFileExecOptions;    BOOLEAN                 BeingDebugged;    BOOLEAN                 Spare;    PVOID                   Mutant;    PVOID                   ImageBaseAddress;} PEB, *PPEB;typedef struct _INITIAL_TEB {    PVOID                   StackBase;    PVOID                   StackLimit;    PVOID                   StackCommit;    PVOID                   StackCommitMax;    PVOID                   StackReserved;} INITIAL_TEB, *PINITIAL_TEB;typedef struct _THREAD_BASIC_INFORMATION {    NTSTATUS                ExitStatus;    PVOID                   TebBaseAddress;    CLIENT_ID               ClientId;    KAFFINITY               AffinityMask;    KPRIORITY               Priority;    KPRIORITY               BasePriority;} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;typedef struct _THREAD_TIMES_INFORMATION {    LARGE_INTEGER           CreationTime;    LARGE_INTEGER           ExitTime;    LARGE_INTEGER           KernelTime;    LARGE_INTEGER           UserTime;} THREAD_TIMES_INFORMATION, *PTHREAD_TIMES_INFORMATION;#pragma pack(push, 1)////  Structure to inject into remote process. Contains //  function pointers and code to execute.//typedef DWORD(WINAPI *pfnWaitForSingleObject)(    _In_ HANDLE hHandle,    _In_ DWORD  dwMilliseconds    );typedef HRESULT(*pfnCloseHandle)(    HANDLE hHandle    );typedef BOOL(*WINAPI    pfnDeleteFile)(        __in LPSTR lpFileName        );typedef VOID(WINAPI *pfnSleep)(    _In_ DWORD dwMilliseconds    );typedef VOID(WINAPI *pfnExitProcess)(    _In_ UINT uExitCode    );typedef BOOL(WINAPI *    pfnRemoveDirectory)(        __in LPSTR lpPathName        );typedef DWORD(WINAPI*    pfnGetLastError)(        VOID        );typedef HMODULE(WINAPI    *pfnLoadLibrary)(        __in LPSTR lpLibFileName        );typedef FARPROC(WINAPI    *pfnGetProcAddress)(        __in HMODULE hModule,        __in LPCSTR lpProcName        );typedef struct _SELFDEL{    HANDLE  hParent;                // parent process handle    pfnWaitForSingleObject  fnWaitForSingleObject;    pfnCloseHandle  fnCloseHandle;    pfnDeleteFile   fnDeleteFile;    pfnSleep    fnSleep;    pfnExitProcess  fnExitProcess;    pfnRemoveDirectory fnRemoveDirectory;    pfnGetLastError fnGetLastError;    pfnLoadLibrary fnLoadLibrary;    pfnGetProcAddress fnGetProcAddress;    BOOL    fRemDir;    TCHAR   szFileName[MAX_PATH];   // file to delete} SELFDEL;#pragma pack(pop)////  Routine to execute in remote process. //typedef int(WINAPI    *pfnMessageBox)(        __in_opt HWND hWnd,        __in_opt LPCWSTR lpText,        __in_opt LPCWSTR lpCaption,        __in UINT uType);// 这段代码用来生成下面的ShellCode// 这段ShellCode 的生成相对比较随意,因为函数是线性执行且最后一个函数为ExitProcess 退出整个进程static void remote_thread(){    SELFDEL *remote = (SELFDEL *)0xFFFFFFFF;// 这个地址到时候替换成真的地址。    pfnLoadLibrary  fnLoadLibrary = remote->fnLoadLibrary;    pfnGetProcAddress   fnGetProcAddress = remote->fnGetProcAddress;    pfnMessageBox   fnMessageBox;    // 等待自删除进程退出    remote->fnWaitForSingleObject(remote->hParent, INFINITE);    remote->fnCloseHandle(remote->hParent);    //    // 确保删除文件,Wile 循环确保函数执行成功    //    while (!remote->fnDeleteFile(remote->szFileName))    {        remote->fnSleep(1000);    }    __asm    {        push    0x00004c4c;             // user32.dll 只能使用栈内存,否则字符串会保存到此进程的只读数据区域        push    0x642e3233;        push    0x72657375;        push    esp;        call    pfnLoadLibrary;        push    0x0041786f;             // "MessageBoxA"        push    0x42656761;        push    0x7373654d;        push    esp;        push    eax;                    // eax 为 user32.dll 的HMOUDLE(基地址)        call    pfnGetProcAddress;      // pointer to MessageBoxA is in EAX        mov     fnMessageBox, eax;        push    0x00657465;             // setup caption text 'self-delete'        push    0x6c65642d;        push    0x666c6573;        mov     eax, esp;        push    0x002e0000;             // setup message text "Entry point hijacked"        push    0x64656b63;        push    0x616a6968;        push    0x20746e69;        push    0x6f702079;        push    0x72746e45;        mov     ebx, esp;        push    MB_OK;        push    eax;        push    ebx;        push    0x00;        call    pfnMessageBox;          // 提示我们的操作成功    }    //    // 直接退出进程    //    remote->fnExitProcess(0);}//// Gets the address of the entry point routine given a// handle to a process and its primary thread.//ULONG_PTR GetProcessEntryPointAddress(HANDLE hProcess, HANDLE hThread){    PEB                 peb;    DWORD               read;    DWORD               dwFSBase;    ULONG_PTR               dwImageBase, dwOffset;    DWORD               dwOptHeaderOffsetImageBase;    DWORD           dwImageBaseEntry;    ULONG_PTR           ulPeb = 0;    typedef        NTSTATUS(WINAPI *pfnNtQueryInformationProcess)        (HANDLE ProcessHandle, ULONG ProcessInformationClass,            PVOID ProcessInformation, UINT32 ProcessInformationLength,            UINT32* ReturnLength);    pfnNtQueryInformationProcess NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");    if (NtQueryInformationProcess) {        typedef struct _PROCESS_BASIC_INFORMATION {            PVOID Reserved1;            PPEB PebBaseAddress;            PVOID Reserved2[2];            ULONG_PTR UniqueProcessId;            PVOID Reserved3;        } PROCESS_BASIC_INFORMATION;        PROCESS_BASIC_INFORMATION   Pbi = { 0 };        UINT32  dwRetLength = 0;        if (NtQueryInformationProcess(hProcess, /*ProcessBasicInformation*/0, &Pbi, sizeof(PROCESS_BASIC_INFORMATION), &dwRetLength) >= 0)        {            ulPeb = (ULONG_PTR)Pbi.PebBaseAddress;        }        else        {            printf("GetLastError %d\r\n", GetLastError());        }    }    printf("%x\r\n", ulPeb);    getchar();    ReadProcessMemory(hProcess, (LPCVOID)ulPeb, &peb, sizeof(PEB), &read);    //    // 通过DOS头->NT头->ImageBase 得到入口地址 两次读取操作    //    dwImageBase = (ULONG_PTR)peb.ImageBaseAddress;    printf("%x\r\n", dwImageBase);    getchar();    ReadProcessMemory(hProcess, (LPCVOID)(dwImageBase + 0x3C), &dwOffset, sizeof(DWORD), &read);    printf("%x\r\n", dwOffset);    getchar();    // dwOffset 为NT 头地址偏移    dwOptHeaderOffsetImageBase = (dwImageBase + dwOffset + (ULONG_PTR)&PIMAGE_NT_HEADERS(0)->OptionalHeader - 0 + (ULONG_PTR)&PIMAGE_OPTIONAL_HEADER(0)->AddressOfEntryPoint - 0);    ReadProcessMemory(hProcess, (LPCVOID)dwOptHeaderOffsetImageBase, &dwImageBaseEntry, sizeof(dwImageBaseEntry), &read);    printf("%x\r\n", dwImageBaseEntry);    getchar();    return (dwImageBase + dwImageBaseEntry);}////  自删除的实现//  BOOL SelfDelete(BOOL fRemoveDirectory){    STARTUPINFO         si = { sizeof(si) };    PROCESS_INFORMATION pi;    _CONTEXT            context;    DWORD               oldProt;    SELFDEL             local;    DWORD               entrypoint;    DWORD               data;    TCHAR               szExe[MAX_PATH] = _T("calc.exe");    ULONG_PTR               process_entry;    //    // this shellcode self-deletes and then shows a messagebox    //    char shellcode[] = {        '\x55', '\x8B', '\xEC', '\x83', '\xEC', '\x10', '\x53', '\xC7', '\x45', '\xF0',        '\xFF', '\xFF', '\xFF', '\xFF',                             // replace these 4 bytes with actual address        '\x8B', '\x45', '\xF0', '\x8B', '\x48', '\x20', '\x89', '\x4D', '\xF4', '\x8B',        '\x55', '\xF0', '\x8B', '\x42', '\x24', '\x89', '\x45', '\xFC', '\x6A', '\xFF',        '\x8B', '\x4D', '\xF0', '\x8B', '\x11', '\x52', '\x8B', '\x45', '\xF0', '\x8B',        '\x48', '\x04', '\xFF', '\xD1', '\x8B', '\x55', '\xF0', '\x8B', '\x02', '\x50',        '\x8B', '\x4D', '\xF0', '\x8B', '\x51', '\x08', '\xFF', '\xD2', '\x8B', '\x45',        '\xF0', '\x83', '\xC0', '\x2C', '\x50', '\x8B', '\x4D', '\xF0', '\x8B', '\x51',        '\x0C', '\xFF', '\xD2', '\x85', '\xC0', '\x75', '\x0F', '\x68', '\xE8', '\x03',        '\x00', '\x00', '\x8B', '\x45', '\xF0', '\x8B', '\x48', '\x10', '\xFF', '\xD1',        '\xEB', '\xDE', '\x68', '\x4C', '\x4C', '\x00', '\x00', '\x68', '\x33', '\x32',        '\x2E', '\x64', '\x68', '\x75', '\x73', '\x65', '\x72', '\x54', '\xFF', '\x55',        '\xF4', '\x68', '\x6F', '\x78', '\x41', '\x00', '\x68', '\x61', '\x67', '\x65',        '\x42', '\x68', '\x4D', '\x65', '\x73', '\x73', '\x54', '\x50', '\xFF', '\x55',        '\xFC', '\x89', '\x45', '\xF8', '\x68', '\x65', '\x74', '\x65', '\x00', '\x68',        '\x2D', '\x64', '\x65', '\x6C', '\x68', '\x73', '\x65', '\x6C', '\x66', '\x8B',        '\xC4', '\x68', '\x00', '\x00', '\x2E', '\x00', '\x68', '\x63', '\x6B', '\x65',        '\x64', '\x68', '\x68', '\x69', '\x6A', '\x61', '\x68', '\x69', '\x6E', '\x74',        '\x20', '\x68', '\x79', '\x20', '\x70', '\x6F', '\x68', '\x45', '\x6E', '\x74',        '\x72', '\x8B', '\xDC', '\x6A', '\x00', '\x50', '\x53', '\x6A', '\x00', '\xFF',        '\x55', '\xF8', '\x6A', '\x00', '\x8B', '\x55', '\xF0', '\x8B', '\x42', '\x14',        '\xFF', '\xD0', '\x5B', '\x8B', '\xE5', '\x5D', '\xC3'    };    //    //  创建暂停线程    //    if (CreateProcess(0, szExe, 0, 0, 0, CREATE_SUSPENDED | IDLE_PRIORITY_CLASS, 0, 0, &si, &pi))    {        local.fnWaitForSingleObject = (pfnWaitForSingleObject)WaitForSingleObject;        local.fnCloseHandle = (pfnCloseHandle)CloseHandle;        local.fnDeleteFile = (pfnDeleteFile)DeleteFile;        local.fnSleep = (pfnSleep)Sleep;        local.fnExitProcess = (pfnExitProcess)ExitProcess;        local.fnRemoveDirectory = (pfnRemoveDirectory)RemoveDirectory;        local.fnGetLastError = (pfnGetLastError)GetLastError;        local.fnLoadLibrary = (pfnLoadLibrary)LoadLibrary;        local.fnGetProcAddress = (pfnGetProcAddress)GetProcAddress;        local.fRemDir = fRemoveDirectory;        //        // 将我们的进程句柄复制到对方的句柄表中,目标进程等待我们的进程执行完毕才开始删除文件。        //        DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(),            pi.hProcess, &local.hParent, 0, FALSE, 0);        GetModuleFileName(0, local.szFileName, MAX_PATH);        //        // 得到进程的入口地址        //        process_entry = GetProcessEntryPointAddress(pi.hProcess, pi.hThread);        //        // 替换我们的数据结构的地址        //        data = process_entry + sizeof(shellcode);        shellcode[13] = (char)(data >> 24);        shellcode[12] = (char)((data >> 16) & 0xFF);        shellcode[11] = (char)((data >> 8) & 0xFF);        shellcode[10] = (char)(data & 0xFF);        //        // 修改其入口代码        //        VirtualProtectEx(pi.hProcess,            (PVOID)process_entry,            sizeof(local) + sizeof(shellcode),            PAGE_EXECUTE_READWRITE,            &oldProt);        WriteProcessMemory(pi.hProcess,            (PVOID)process_entry,            shellcode,            sizeof(shellcode), 0);        WriteProcessMemory(pi.hProcess,            (PVOID)data,            &local,            sizeof(local), 0);        //        // 执行我们自己的代码        //        ResumeThread(pi.hThread);        CloseHandle(pi.hThread);        CloseHandle(pi.hProcess);        return TRUE;    }    return FALSE;}int main(void){    SelfDelete(TRUE);    return 0;}