<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    常見的幾種DLL注入技術

    VSole2021-12-18 16:04:06

    簡介

    這次實驗是在WIN7 X86系統上進程,使用的編譯器是VS2017。

    所謂的DLL注入,其實就是在其他的進程中把我們編寫的DLL加載進去。如下圖所示:

    而加載Dll的API就是LoadLibrary,它的參數是保存要加載的DLL的路徑的地址。所以DLL注入的核心就是把要注入的DLL的路徑寫到目標進程中,然后在目標進程中調用LoadLibrary函數,并且指定參數為保存了DLL路徑的地址。

    要實現DLL注入,首先就要創建一個用來注入的DLL。在VS2017中要生成一個DLL項目,只需要向下圖這樣創建一個DLL工程就好。

    在生成的文件中,有個dllmain.cpp,打開以后內容如下:

    當DLL的狀態發生變化的時候,就會調用DllMain函數。而傳遞的ul_reason_for_call這個參數代表了4種不同的狀態變化的情況,我們就可以根據這四種不同的狀態根據需要來寫出相應的代碼,就會讓注入的DLL執行我們需要的功能。

    ul_reason_for_call的值代表的狀態DLL_PROCESS_ATTACHDll剛剛映射到進程空間中DLL_THREAD_ATTACH進程中有新線程創建DLL_THREAD_DETACH進程中有新線程銷毀DLL_PROCESS_DETACH

    Dll從進程空間中接觸映射

    不過在實現DLL注入的時候用的DLL幾乎都是在Dll剛剛映射到進程空間的時候就執行相關的代碼。比如像下面這樣,創建一個新線程來執行代碼,這里在桌面打開一個文件來并寫入加載這個DLL的進程的完成路徑名。由于是獨占方式打開,此時如果多個線程同時打開這個文件,CreateFile就會出錯,錯誤碼就會是32,根據這個來對線程進行休眠,等其他線程使用完了,再次打開文件進行操作。

    // dllmain.cpp : 定義 DLL 應用程序的入口點。#include #include #pragma comment(lib, "shell32.lib") #define FILE_NAME "result.txt" DWORD WINAPI ThreadProc(LPVOID lpParameter){    HANDLE hFile = NULL;    CHAR szDesktopFile[MAX_PATH] = { 0 };  //保存系統桌面路徑    CHAR szFullFilePath[MAX_PATH] = { 0 }; //保存完成的加載DLL文件的文件路徑    DWORD dwRetLen = 0, dwFileLen = 0;    BOOL bRet = TRUE;     //獲取桌面路徑    bRet = SHGetSpecialFolderPath(NULL, szDesktopFile, CSIDL_DESKTOP, TRUE);    if (bRet)    {        strcat(szDesktopFile, "\\");        strcat(szDesktopFile, FILE_NAME);        while (TRUE)        {            hFile = CreateFile( szDesktopFile,                                GENERIC_READ | GENERIC_WRITE,                                0, NULL,                                OPEN_ALWAYS,                                FILE_ATTRIBUTE_NORMAL, NULL);            if (hFile == INVALID_HANDLE_VALUE)   //打開文件錯誤            {                if (GetLastError() == 32)    //錯誤碼是不是其他進程正在使用這個文件,是的話等待一會在繼續打開                {                    Sleep(200);                    continue;                }                else break;            }            else            {                GetModuleFileName(NULL, szFullFilePath, MAX_PATH);    //獲取加載DLL的進程的完整路徑                dwFileLen = strlen(szFullFilePath);                szFullFilePath[dwFileLen] = '\r'; //由于是在WIN7運行,換行符是\r                szFullFilePath[dwFileLen + 1] = '';                SetFilePointer(hFile, 0, NULL, FILE_END);                WriteFile(hFile, szFullFilePath, dwFileLen + 2, &dwRetLen, NULL);                if (hFile) CloseHandle(hFile);                break;            }        }    }         return 0;}  BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved                     ){    switch (ul_reason_for_call)    {        case DLL_PROCESS_ATTACH:        {            HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);            if (hThread) CloseHandle(hThread);            break;        }        case DLL_THREAD_ATTACH:        case DLL_THREAD_DETACH:        case DLL_PROCESS_DETACH:            break;    }    return TRUE;}
    

    點擊生成解決方案以后就可以在項目目錄下找到相應的DLL文件,如下圖。這個文件就是用來注入到其他進程的DLL。

    代碼框架

    由于要編寫的代碼中,只有注入功能不同,但是其他的輔助功能。比如,提權,獲取進程PID等等是一樣的,為了避免重復就先在這給出代碼的框架。后面的不同注入技術只需根據需要加進去就好。注意,如果想要提權成功,需要用管理員權限運行代碼。

    #include #include #include  #define PROCESS_NAME "taskmgr.exe"    //要注入的進程名,這個是任務管理器的進程名#define DLL_NAME "InjectDll.dll"  //要注入的DLL的名稱 BOOL InjectDll(DWORD dwPid, CHAR szDllName[]);  //注入DLLDWORD GetPID(PCHAR pProName); //根據進程名獲取PIDVOID ShowError(PCHAR msg);    //打印錯誤信息BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName);    //提升進程權限 int main(){    CHAR szDllPath[MAX_PATH] = { 0 };  //保存要注入的DLL的路徑    DWORD dwPID = 0;                 //保存要注入的進程的PID     // 提升當前進程令牌權限    if (!EnbalePrivileges(GetCurrentProcess(), SE_DEBUG_NAME))    {        printf("權限提升失敗");    }     dwPID = GetPID(PROCESS_NAME);    if (dwPID == 0)    {        printf("沒有找到要注入的進程");        goto exit;    }         GetCurrentDirectory(MAX_PATH, szDllPath);  //獲取程序的目錄    strcat(szDllPath, "\\");    strcat(szDllPath, DLL_NAME);               //與DLL名字拼接得到DLL的完整路徑    printf("要注入的進程名:%s PID:%d", PROCESS_NAME, dwPID);    printf("要注入的DLL的完整路徑%s", szDllPath);     if (InjectDll(dwPID, szDllPath))    {        printf("Dll注入成功");    }exit:    system("pause");     return 0;} BOOL InjectDll(DWORD dwPid, CHAR szDllName[]){    BOOL bRet = TRUE;     return bRet;} DWORD GetPID(PCHAR pProName){    PROCESSENTRY32 pe32 = { 0 };    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    BOOL bRet = FALSE;    DWORD dwPID = 0;     if (hSnap == INVALID_HANDLE_VALUE)    {        printf("CreateToolhelp32Snapshot process %d", GetLastError());        goto exit;    }     pe32.dwSize = sizeof(pe32);    bRet = Process32First(hSnap, &pe32);    while (bRet)    {        if (lstrcmp(pe32.szExeFile, pProName) == 0)        {            dwPID = pe32.th32ProcessID;            break;        }        bRet = Process32Next(hSnap, &pe32);    }     CloseHandle(hSnap);exit:    return dwPID;} VOID ShowError(PCHAR msg){    printf("%s Error %d", msg, GetLastError());} BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName){    HANDLE hToken = NULL;    LUID luidValue = { 0 };    TOKEN_PRIVILEGES tokenPrivileges = { 0 };    BOOL bRet = FALSE;    DWORD dwRet = 0;      // 打開進程令牌并獲取具有 TOKEN_ADJUST_PRIVILEGES 權限的進程令牌句柄    if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken))    {        ShowError("OpenProcessToken");        goto exit;    }     // 獲取本地系統的 pszPrivilegesName 特權的LUID值    if (!LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue))    {        ShowError("LookupPrivilegeValue");        goto exit;    }     // 設置提升權限信息    tokenPrivileges.PrivilegeCount = 1;    tokenPrivileges.Privileges[0].Luid = luidValue;    tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;    // 提升進程令牌訪問權限    if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL))    {        ShowError("AdjustTokenPrivileges");        goto exit;    }    else    {        // 根據錯誤碼判斷是否特權都設置成功        dwRet = ::GetLastError();        if (ERROR_SUCCESS == dwRet)        {            bRet = TRUE;            goto exit;        }        else if (ERROR_NOT_ALL_ASSIGNED == dwRet)        {            ShowError("ERROR_NOT_ALL_ASSIGNED");            goto exit;        }    }exit:    return bRet;}
    

    遠程線程注入

    這種注入方式可以說是最常用的注入方式了,它的核心就是調用Windows提供的CreateRemoteThread函數。該函數可以在其他的進程空間中創建一個新的線程進行執行,該函數在文檔中的定義如下:

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

    參數說明hProcess要創建線程的進程句柄lpThreadAttributes新線程的安全描述符dwStackSize堆棧起始大小,為0表示默認大小lpStartAddress表示要運行線程的起始地址lpParameter保存要傳遞給線程參數的地址dwCreationFlags控制線程創建的標志,為0表示創建后立即執行lpThreadId指向接收線程標識符變量的指針。為NULL表示不返回線程標識符

    其中的關鍵三個參數分別是:

    (1)hProcess用來指定在哪個進程中創建新線程。

    (2)lpStartAddress用來指定將進程中的哪個地址開始作為新線程運行的起始地址。

    (3)lpParameter保存的也是一個地址,這個地址中保存的就是新線程要用到的參數。

    那也就是說只要我們指定了一個地址給lpStartAddress,那么我們就可以在其他進程中創建一個線程來執行程序。而再看加載DLL的LoadLibrary函數在文檔中的定義如下:

    HMODULE WINAPI LoadLibrary(__in  LPCTSTR lpFileName);
    

    可以看到,這個函數同樣也只需要一個參數,這個參數是一個地址,而這個地址中保存的是我們要加載的DLL的名稱的字符串。

    根據這些,不難想到,只要我們可以獲取新進程中的LoadLibrary函數的地址以及包含有要加載的DLL的字符串的地址就可以通過CreateRemoteThread函數來成功開起一個線程執行LoadLibrary函數來加載我們的DLL。

    那么現在的問題就是如何獲得LoadLibrary函數的地址以及保存有要加載的DLL路徑的字符串的地址。

    對于LoadLibrary函數,由于它是在常用的系統DLL,也就是KERNEL32.dll中,所以這個DLL是可以按照它的ImageBase成功裝載到每個進程的空間中。這樣的話Kernel32.dll在每個進程中的起始地址是一樣的,那么LoadLibrary函數的地址也就會一樣。那么我們就可以在本進程中查找LoadLibrary函數的地址,并且完全可以相信,在要注入DLL的進程中LoadLibrary的地址也是這個。

    至于DLL名稱的字符串,我們可以通過在進程中申請一塊可以將DLL完整路徑寫入的內存,并在這個內存中將DLL的完整路徑寫入,將寫入到注入進程DLL完整路徑的內存地址作為參數就可以實現進程的注入。

    具體代碼如下:

    BOOL InjectDll(DWORD dwPid, CHAR szDllName[]){    BOOL bRet = TRUE;    HANDLE hProcess = NULL, hRemoteThread = NULL;    HMODULE hKernel32 = NULL;    DWORD dwSize = 0;    LPVOID pDllPathAddr = NULL;    PVOID pLoadLibraryAddr = NULL;     // 打開注入進程,獲取進程句柄    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);    if (NULL == hProcess)    {        ShowError("OpenProcess");        bRet = FALSE;        goto exit;    }     // 在注入進程中申請可以容納DLL完成路徑名的內存空間    dwSize = 1 + strlen(szDllName);    pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);    if (!pDllPathAddr)    {        ShowError("VirtualAllocEx");        bRet = FALSE;        goto exit;    }     // 把DLL完整路徑名寫入進程中    if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))    {        ShowError("WriteProcessMemory");        bRet = FALSE;        goto exit;    }          hKernel32 = LoadLibrary("kernel32.dll");    if (hKernel32 == NULL)    {        ShowError("LoadLibrary");        bRet = FALSE;        goto exit;    }     // 獲取LoadLibraryA函數地址    pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");    if (pLoadLibraryAddr == NULL)    {        ShowError("GetProcAddress ");        bRet = FALSE;        goto exit;    }     //創建遠程線程進行DLL注入    hRemoteThread = CreateRemoteThread(hProcess, NULL, 0,                       (LPTHREAD_START_ROUTINE)pLoadLibraryAddr,                       pDllPathAddr, 0, NULL);    if (hRemoteThread == NULL)    {        ShowError("CreateRemoteThread");        bRet = FALSE;        goto exit;    }     exit:    if (hKernel32) FreeLibrary(hKernel32);    if (hProcess) CloseHandle(hProcess);    if (hRemoteThread) CloseHandle(hRemoteThread);         return bRet;}
    

    加強版遠程線程注入

    上面的方法雖然可以方便的注入DLL,但是在WIN7、WIN10系統上,會由于SESSION 0隔離機制從而導致只能成功注入普通的用戶進程,如果注入系統進程就會導致失敗。

    而經過逆向分析發現,使用Kernel32.dll中的CreateRemoteThread進行注入的時候,程序會走到ntdll.dll中的ZwCreateThreadEx函數進行執行。這是一個未導出的函數,所以需要手動獲取函數地址來進行調用,相比于CreateRemoteThread更加底層。這個函數在64位和32位系統中的函數聲明也不相同,在32位中的聲明如下:

    typedef DWORD(WINAPI *pFnZwCreateThreadEx)(        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位中的聲明如下:

    typedef DWORD(WINAPI *pFnZwCreateThreadEx)(        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);
    

    根據逆向分析的結果,在內核6.0(WIN7, WIN10)等系統上調用CreateRemoteThread的時候,當程序走到ZwCreateThreaEx的時候它第7個參數,也就是CreateThreadFlags會被設置為1,如下圖:

    它會導致線程創建的時候就被掛起,隨后查看要運行的進程所在的會話層之后再決定是否要恢復線程的運行。所以要破解這種情況只需要將第7個參數設為0就可以,相應代碼如下:

    typedef DWORD(WINAPI *pFnZwCreateThreadEx)(PHANDLE, ACCESS_MASK, LPVOID,                        HANDLE, LPTHREAD_START_ROUTINE,                       LPVOID, BOOL, DWORD, DWORD, DWORD, LPVOID);                     BOOL InjectDll(DWORD dwPid, CHAR szDllName[]){    BOOL bRet = TRUE;    HANDLE hProcess = NULL, hRemoteThread = NULL;    HMODULE hKernel32 = NULL, hNtDll = NULL;    DWORD dwSize = 0;    LPVOID pDllPathAddr = NULL;    PVOID pLoadLibraryAddr = NULL;    pFnZwCreateThreadEx ZwCreateThreadEx = NULL;     // 打開注入進程,獲取進程句柄    hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);    if (NULL == hProcess)    {        ShowError("OpenProcess");        bRet = FALSE;        goto exit;    }     // 在注入進程中申請可以容納DLL完成路徑名的內存空間    dwSize = 1 + strlen(szDllName);    pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);    if (!pDllPathAddr)    {        ShowError("VirtualAllocEx");        bRet = FALSE;        goto exit;    }     // 把DLL完成路徑名寫入進程中    if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))    {        ShowError("WriteProcessMemory");        bRet = FALSE;        goto exit;    }          hKernel32 = LoadLibrary("kernel32.dll");    if (hKernel32 == NULL)    {        ShowError("LoadLibrary kernel32");        bRet = FALSE;        goto exit;    }     // 獲取LoadLibraryA函數地址    pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");    if (pLoadLibraryAddr == NULL)    {        ShowError("GetProcAddress LoadLibraryA");        bRet = FALSE;        goto exit;    }     hNtDll = LoadLibrary("ntdll.dll");    if (hNtDll == NULL)    {        ShowError("LoadLibrary ntdll");        bRet = FALSE;        goto exit;    }         ZwCreateThreadEx = (pFnZwCreateThreadEx)GetProcAddress(hNtDll, "ZwCreateThreadEx");    if (!ZwCreateThreadEx)    {        ShowError("GetProcAddress ZwCreateThreadEx");        bRet = FALSE;        goto exit;    }     ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,                      hProcess, (LPTHREAD_START_ROUTINE)pLoadLibraryAddr,                     pDllPathAddr, 0, 0, 0, 0, NULL);    if (hRemoteThread == NULL)    {        ShowError("ZwCreateThreadEx");        bRet = FALSE;        goto exit;    }exit:    if (hKernel32) FreeLibrary(hKernel32);    if (hNtDll) FreeLibrary(hNtDll);    if (hProcess) CloseHandle(hProcess);    if (hRemoteThread) CloseHandle(hRemoteThread);    return bRet;}
    

    APC注入

    在Windows系統中,每個線程都會維護一個自己的APC隊列,這個APC隊列中保存了要求線程執行的一些APC函數。對于用戶模式的APC隊列,當線程處在可警告狀態時,就會執行這些APC函數。而要往APC隊列中增加APC函數,需要通過QueueUserAPC函數來實現,這個函數在文檔中的定義如下:

    DWORD WINAPI QueueUserAPC(  __in  PAPCFUNC pfnAPC,  __in  HANDLE hThread,  __in  ULONG_PTR dwData);
    

    參數

    說明

    pfnAPC

    當滿足條件時,要執行的APC函數的地址

    hThread

    指定增加APC函數的線程句柄

    dwData

    要執行的APC函數參數地址

    可以看到pfnAPC和dwData這兩個參數和CreateRemoteThread中的lpStartAddress和lpParameter的作用是一樣的。不過這里是對線程進行操作,一個進程有多個線程。所以為了確保程序正確運行,所以需要遍歷所有線程,查看是否是要注入的進程的線程,依次獲得句柄插入APC函數。具體代碼如下:

    BOOL InjectDll(DWORD dwPid, CHAR szDllName[]){    BOOL bRet = TRUE;    HANDLE hProcess = NULL, hThread = NULL, hSnap = NULL;    HMODULE hKernel32 = NULL;    DWORD dwSize = 0;    PVOID pDllPathAddr = NULL;    PVOID pLoadLibraryAddr = NULL;    THREADENTRY32 te32 = { 0 };     // 打開注入進程,獲取進程句柄    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);    if (NULL == hProcess)    {        ShowError("OpenProcess");        bRet = FALSE;        goto exit;    }     // 在注入進程中申請可以容納DLL完成路徑名的內存空間    dwSize = 1 + strlen(szDllName);    pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);    if (!pDllPathAddr)    {        ShowError("VirtualAllocEx");        bRet = FALSE;        goto exit;    }     // 把DLL完成路徑名寫入進程中    if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))    {        ShowError("WriteProcessMemory");        bRet = FALSE;        goto exit;    }      hKernel32 = LoadLibrary("kernel32.dll");    if (hKernel32 == NULL)    {        ShowError("LoadLibrary");        bRet = FALSE;        goto exit;    }     // 獲取LoadLibraryA函數地址    pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");    if (pLoadLibraryAddr == NULL)    {        ShowError("GetProcAddress");        bRet = FALSE;        goto exit;    }     //獲得線程快照    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);    if (!hSnap)    {        ShowError("CreateToolhelp32Snapshot");        bRet = FALSE;        goto exit;    }     //遍歷線程    te32.dwSize = sizeof(te32);    if (Thread32First(hSnap, &te32))    {        do        {            //這個線程的進程ID是不是要注入的進程的PID            if (te32.th32OwnerProcessID == dwPid)            {                hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);                if (hThread)                {                    QueueUserAPC((PAPCFUNC)pLoadLibraryAddr, hThread, (ULONG_PTR)pDllPathAddr);                    CloseHandle(hThread);                    hThread = NULL;                }                else                {                    ShowError("OpenThread");                    bRet = FALSE;                    goto exit;                }            }        } while (Thread32Next(hSnap, &te32));    }exit:    if (hKernel32) FreeLibrary(hKernel32);    if (hProcess) CloseHandle(hProcess);    if (hThread) CloseHandle(hThread);    return bRet;}
    

    AppInit_DLLs注入

    這種注入方式主要是通過修改注冊表中HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows中的AppInit_DLLs和LoadAppInit_Dlls,如下圖:

    只要將AppInit_DLLs設置為要注入的DLL的路徑并且將LoadAppInit_DLLs的值改成1。那么,當程序重啟的時候,所有加載user32.dll的進程都會根據AppInit_Dlls中的DLL路徑加載指定的DLL。

    所以這種DLL注入的實現代碼如下:

    BOOL InjectDll(DWORD dwPid, CHAR szDllName[]){    BOOL bRet = TRUE;    HKEY hKey = NULL;    CHAR szAppKeyName[] = { "AppInit_DLLs" };    CHAR szLoadAppKeyName[] = { "LoadAppInit_DLLs" };    DWORD dwLoadAppInit = 1; //設置LoadAppInit_DLLs的值     //打開相應注冊表鍵    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows",        0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)    {        ShowError("RegOpenKeyEx");        bRet = FALSE;        goto exit;    }     //設置AppInit_DLLs為相應的DLL路徑    if (RegSetValueEx(hKey, szAppKeyName, 0, REG_SZ, (PBYTE)szDllName, strlen(szDllName) + 1) != ERROR_SUCCESS)    {        ShowError("RegSetValueEx");        bRet = FALSE;        goto exit;    }     //將LoadAppInit_DLLs的值設為1    if (RegSetValueEx(hKey, szLoadAppKeyName, 0, REG_DWORD, (PBYTE)&dwLoadAppInit, sizeof(dwLoadAppInit)) != ERROR_SUCCESS)    {        ShowError("RegSetValueEx");        bRet = FALSE;        goto exit;    }exit:    return bRet;}
    

    運行程序以后,會發現相應的鍵值已經被設置。

    全局鉤子注入

    Windows系統中的大多數應用都是基于消息機制的,也就是說它們都有一個消息過程函數,可以根據收到的不同消息來執行不同的代碼。基于這種消息機制,Windows維護了一個OS message queue以及為每個程序維護著一個application message queue。當發生各種事件的時候,比如敲擊鍵盤,點擊鼠標等等,操作系統會從OS message queue將消息取出給到相應的程序的application message queue。

    而OS message queue和application message queue的中間有一個稱為鉤鏈的結果如下:

    在這個鉤鏈中保存的就是設置的各種鉤子函數,而這些鉤子函數會比應用程序還早接收到消息并對消息進行處理。所以程序員可以通過在鉤子中設置鉤子函數,而要設置鉤子函數就需要使用SetWindowHookEx來將鉤子函數安裝到鉤鏈中,函數在文檔中的定義如下:

    HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);
    

    參數

    說明

    idHook

    要安裝的鉤子類型,為了掛全局鉤子,這里選擇WH_GETMESSAGE。表示的是安裝一個掛鉤過程,它監視發送到消息隊列的消息

    lpfn

    表示的是鉤子的回調函數。如果dwThreadId為0,則lpfn指向的鉤子過程必須指向DLL中的鉤子過程

    hMod

    包含由lpfn參數執行的鉤子過程的DLL句柄

    dwThreadId

    與鉤子過程關聯的線程標識符,如果為0則表示與所有線程相關聯。

    如果函數成功,則返回鉤子過程的句柄,否則為NULL。

    根據上面的介紹可以得知,想要創建一個全局鉤子,就必須在DLL文件中創建。這是因為進程的地址空間是獨立的,發生對應事件的進程不能調用其他進程地址空間的鉤子函數。如果鉤子函數的實現代碼在DLL中,則在對應事件發生時,系統會把這個DLL加載到發生事件的進程地址空間中,使它可以調用鉤子函數進行處理。

    所以只要在系統中安裝了全局鉤子,那么只要進程接收到可以發出鉤子的消息,全局鉤子的DLL就會被系統自動或者強行加載到進程空間中,這就可以實現DLL注入。

    而這里之所以設置為WH_GETMESSAGE,是因為這種類型的鉤子會監視消息隊列,又因為Windows系統是基于消息驅動的,所以所有的進程都會有自己的一個消息隊列,都會加載WH_GETMESSAGE類型的全局鉤子。

    由于設置全局鉤子的代碼需要在DLL文件中完成,所以首先需要新建一個InjectDll.cpp。

    隨后在文件中寫入如下設置全局鉤子的函數:

    extern HMODULE g_hDllModule;// 設置全局鉤子BOOL SetGlobalHook(){    g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);    if (NULL == g_hHook)    {        return FALSE;    }    return TRUE;}
    

    其中的回調函數的實現如下:

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

    這里只是簡單的調用CallNextHookEx函數表示將當前鉤子傳遞給鉤鏈中的下一個鉤子,第一個參數要指定當前鉤子的句柄。如果直接返回0,則表示中斷鉤子傳遞,這就實現了對鉤子進行攔截。

    而g_hDllModule則是在DLL加載的時候被賦值的。

    當鉤子不再使用,可以卸載掉全局鉤子,這樣此時已經包含鉤子回調函數的DLL模塊的進程就會釋放DLL模塊。卸載鉤子的代碼如下:

    // 卸載鉤子BOOL UnSetGlobalHook(){    if (g_hHook)    {        UnhookWindowsHookEx(g_hHook);    }    return TRUE;}
    

    上面的全局鉤子的設置,鉤子回調函數的實現以及全局鉤子的卸載都需要使用到全局鉤子的句柄。為了讓任意一個獨立的進程中對句柄的修改都可以影響到其他進程,就需要在DLL中使用共享內存的,來保證將DLL中加載到多個進程以后,一個進程對它的修改可以影響到其他進程。設置共享內存的方式如下:

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

    而為了調用設置鉤子和卸載鉤子的函數,就需要創建一個.def文件來將兩個函數導出。

    此時使用PEID查看InjectDll.dll可以看到導出表有如下的導出函數:

    接下來只要在代碼中將DLL引入并或者對應的函數對它們進行調用就好。

    BOOL InjectDll(DWORD dwPid, CHAR szDllName[]){    BOOL bRet = TRUE;    HMODULE hDll = NULL;    pFnSetGlobalHook SetGlobalHook = NULL;    pFnUnSetGlobalHook UnSetGlobalHook = NULL;     hDll = LoadLibrary(szDllName);    if (hDll == NULL)    {        ShowError("LoadLibrary");        bRet = FALSE;        goto exit;    }     SetGlobalHook = (pFnSetGlobalHook)GetProcAddress(hDll, "SetGlobalHook");    if (SetGlobalHook == NULL)    {        ShowError("GetProcAddress SetGlobalHook");        bRet = FALSE;        goto exit;    }     if (!SetGlobalHook())    {        printf("鉤子安裝失敗");        bRet = FALSE;        goto exit;    }     printf("鉤子安裝成功,按回車卸載鉤子");    system("pause");     UnSetGlobalHook = (pFnUnSetGlobalHook)GetProcAddress(hDll, "UnSetGlobalHook");    if (UnSetGlobalHook == NULL)    {        ShowError("GetProcAddress UnSetGlobalHook");        bRet = FALSE;        goto exit;    }    if (UnSetGlobalHook())    {        printf("已將全局鉤子卸載");    }exit:    return bRet;}
    

    實驗結果

    將編譯好的exe文件和dll文件放到同一路徑中,運行exe以后會在桌面生成一個result.txt文件。打開文件以后會看到里面的內容是被注入的進程的完整的路徑名。

    dll注入loadlibrary
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    初探DLL注入
    2023-02-06 10:35:24
    DLL注入是指向運行中的其它進程強制插入特定的DLL文件。從技術細節來說,DLL注入命令其它進程自行調用LoadLibrary()API,加載用戶指定的DLL文件。從上圖可以看到,test.dll已被強制插入進程。加載到某一進程中的test.dll與已經加載到某一進程中的dll一樣,擁有訪問notepad.exe進程內存的權限。DLL被加載到進程后會自動運行DLLMain()函數,用戶可以把想執行的代碼放到DLLMain()函數,每當加載DLL時,添加的代碼就會得到執行。利用這種特性可以修復程序Bug以及添加新功能
    本文從 dll 注入技術、msf migrate 模塊剖析、檢測思路等方面展開說明。
    反射式DLL注入實現
    2022-05-13 15:59:21
    反射式dll注入與常規dll注入類似,而不同的地方在于反射式dll注入技術自己實現了一個reflective loader()函數來代替LoadLibaryA()函數去加載dll,示意圖如下圖所示。藍色的線表示與用常規dll注入相同的步驟,紅框中的是reflective loader()函數行為,也是下面重點描述的地方。
    Dll注入
    2021-11-08 14:57:41
    最近太忙啦XDM,又在做一些列的分析復現工作量有點大,更新要慢一點了。一致,也不會覆蓋其他的進程信息。
    簡介這次實驗是在WIN7 X86系統上進程,使用的編譯器是VS2017。所謂的DLL注入,其實就是在其他的進程中把我們編寫的DLL加載進去。所以DLL注入的核心就是把要注入DLL的路徑寫到目標進程中,然后在目標進程中調用LoadLibrary函數,并且指定參數為保存了DLL路徑的地址。要實現DLL注入,首先就要創建一個用來注入DLL
    在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數,根據不同消息完成不同功能,windows通過鉤子機制來截獲和監視系統中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現相應的鉤子函數。
    全局鉤子注入在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數,根據不同消息完成不同功能,windows通過鉤子機制來截獲和監視系統中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現相應的鉤子函數。
    Windows注入的一些方式
    當線程從等待狀態蘇醒后,會自動檢測自己得APC隊列中是否存在APC過程。所以只需要將目標進程的線程的APC隊列里面添加APC過程,當然為了提高命中率可以向進程的所有線程中添加APC過程。然后促使線程從休眠中恢復就可以實現APC注入。往線程APC隊列添加APC,系統會產生一個軟中斷。第二個參數表示插入APC的線程句柄,要求線程句柄必須包含THREAD_SET_CONTEXT訪問權限。第三個參數表示傳遞給執行函數的參數。如果直接傳入shellcode不設置第三個函數,可以直接執行shellcode。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类