1. 程式人生 > >檔案自刪除的一些資料與實現

檔案自刪除的一些資料與實現

原文連結

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 path GetModuleFileName(NULL, szFilePath, _MAX_PATH); // Delete specified file DeleteFile(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.exe
c:\dir\new.exe=c:\dir\old.exe

       第一個行代表一個重啟刪除操作,第二行代表一個重新命名操作。

自刪除批處理方法

       這是一個廣為人知的方法。當我們執行下面的這個批處理檔案的時候

del %0.bat

       可能會收到提示說本檔案不存在,但是當我們把後面的路徑改為本批處理檔案的完整路徑的時候,將成功實現自刪除。如果程式想要實現自刪除,可以通過生成一個下面所示的批處理檔案並執行即可。

:Repeat
del "C:\MYDIR\MYPROG.EXE"
if exist "MYPROG.EXE" goto Repeat
rmdir "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 disabled
void __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);

        //