0x00 远线程注入
远线程注入是指一个进程在另一个进程中创建线程的技术。
0x01 函数介绍
OpenProcess
作用: 打开现有的本地进程对象。
函数声明:
HANDLE WINAPI OpenProcess( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId )
参数:
dwDesiredAccess:
想拥有该进程的访问权限,若进程启动了SeDebugPrivilege权限,则无论安全描述符内容是什么,都会授予请求的访问权限。
bInheritHandle:
若该值为TRUE,则此进程创建的进程将继承该句柄。
dwProcessId:
本地进程的PID。
返回值:
成功:返回进程打开句柄
失败:返回NULL
VirtualAllocEx
作用: 在指定进程的虚拟地址空间内保留、提交或更改内存的状态。
函数声明:
LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect )
参数:
hProcess:
过程的句柄。句柄必须有PROCESS_VM_OPERATION(允许远程VM操作)权限。
lpAddress:
指定要分配页面所需起始地址指针。若为NULL,则自动分配内存。
dwSize:
要分配的内存大小,单位为字节。
flAllocationType:
内存分配类型。具体参数参考官方手册。
flProtect:
要分配的页面区域的内存保护。
返回值:
成功:返回分配页面基址
失败:返回NULL
WriteProcessMemory
作用: 在指定的进程中将数据写入内存区域,要写入的整个区域必须可访问,否则操作失败。
函数声明:
BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten )
参数:
hProcess:
要修改的进程内存的句柄。句柄必须具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问权限。
lpBaseAddress:
指向指定进程中写入数据的基地址指针。
lpBuffer:
指向缓冲区的指针,其中包含要写入指定进程的地址空间中的数据。
nSize:
要写入指定进程的字节数。
lpNumberOfBytesWritten:
指向变量的指针,该变量接收传输到指定进程的字节数。
返回值:
成功:返回不为0
失败:返回0
CreateRemoteThread
作用: 在另一个进程的虚拟地址空间中创建运行的线程。
函数声明:
HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_ LPDWORD lpThreadId )
参数:
hProcess:
要创建线程的进程句柄。句柄必须具有PROCESS_CREATE_THREAD、RPOCESS_QUERY_INFORMATION、PROCESS_VM_OPERATION、PROCESS_VM_WRITE和PROCESS_VM_READ访问权限。
lpThreadAttributes:
指向SECURITY_ATTRIBUTES结构的指针,该结构指定新线程的安全描述符,并确定进程是否可以继承返回的句柄。若为NULL,则线程获取默认的安全描述符,不能继承该句柄。
dwStackSize:
堆栈的初始大小,以字节为单位。
lpStartAddress:
指向由线程执行类型为LPTHREAD_START_ROUTINE的应用程序定义的函数指针,并表示远程进程中线程的起始地址,该函数必须存在于远程进程中。
lpParameter:
指向要传递给线程函数的变量的指针。
dwCreationFlags:
控制线程创建的标志。若为0,表示线程在创建后立即运行。
lpThreadId:
指向接收线程标识符的变量的指针。为NULL则不返回线程标识符。
返回值:
成功:返回新线程的句柄
失败:返回NULL
0x02 实现过程
1、获取LoadLibrary函数的地址,对于kernel32.dll的加载基址在每个进程中都是相同的,所以我们能获取LoadLibrary函数的地址。
2、调用VirtualAllocEx函数向目标进程空间申请一块内存。
3、调用WriteProcessMemory函数将指定的DLL路径写入到目标进程空间。
4、通过CreateRemoteThread函数加载LoadLibrary函数的地址,进行DLL注入。
0x03 实例代码
#include <windows.h> #include <stdio.h> BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, wchar_t *pszDllFileName) { HANDLE hProcess = NULL; DWORD dwSize = 0; LPVOID pDllAddr = NULL; FARPROC pFuncProcAddr = NULL; // 打开注入的进程 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { printf("Error OpenProcess,%d", GetLastError()); return FALSE; } // 在注入进程中申请内存 dwSize = 1 + lstrlen(pszDllFileName); pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (pDllAddr == NULL) { printf("Error VirtualAllocEx,%d", GetLastError()); return FALSE; } // 向申请的内存中写入数据 if (FALSE == WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) { printf("Error WriteProcessMemory,%d", GetLastError()); return FALSE; } // 获取LoadLibraryA函数地址 pFuncProcAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (NULL == pFuncProcAddr) { printf("Error GetProcAddress,%d", GetLastError()); return FALSE; } // CreateRemoteThreadc创建远程线程,实现dll注入 HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL); if (NULL == hRemoteThread) { printf("Error CreateRemoteThread,%d", GetLastError()); return FALSE; } CloseHandle(hProcess); return TRUE; } int main() { wchar_t* dllPath = (wchar_t*)"D:\\Dll1.dll"; CreateRemoteThreadInjectDll(9956, dllPath); return 0; }
DLL代码:
该DLL项目由vs2019生成,注入后自动弹出消息框
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { Switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH:{ MessageBoxA(NULL, "Inject is OK!", "OK", MB_OK); break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
0x04 突破Session0隔离的远线程注入
这里使用到一个函数ZwCreateThreadEx,实际上CreateRemoteThread最终是通过调用ZwCreateThreadEx实现远线程创建的,ZwCreateThreadEx更为底层。在内核6.0(Windows VISTA、7、8)之后,由于session隔离机制,在创建进程之后是先挂起进程,检查进程所在的会话层后再决定是否恢复进程。
在CreateRemoteThread函数调用ZwCreateThreadEx函数时,由于ZwCreateThreadEx第七个参数为1,会导致线程创建后一直处于挂起状态,因此我们需要设置ZwCreateThreadEx第七个参数为0。
由于在ntdll.dll中,ZwCreateThreadEx并没有被声明,因此需要使用GetProcAddress导出地址
函数声明:
win64下:
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 )
win32下:
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> BOOL ZwCreateThreadExInjectDLL(DWORD dwProcessId, 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, dwProcessId); if (NULL == hProcess) { printf("Error OpenProcess:%d", GetLastError()); return FALSE; } // 申请内存 dwSize = 1 + ::lstrlen(pszDllFileName); pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (pDllAddr == NULL){ printf("Error VirtualAllocEx:%d", GetLastError()); return FALSE; } // 写入数据 if (FALSE == WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) { printf("Error WriteProcessMemory:%d", GetLastError()); 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 // 加载ntdll.dll HMODULE hNtdllDll = LoadLibrary("ntdll.dll"); if (NULL == hNtdllDll) { printf("Error Load 'ntdll.dll':%d", GetLastError()); return FALSE; } // 获取LoadLibraryA函数地址 pFuncProcAddr = GetProcAddress(::GetModuleHandle("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, pDllAddr, 0, 0, 0, 0, NULL); if (NULL == hRemoteThread) { printf("Error Inject DLL:%u", dwStatus); return FALSE; } CloseHandle(hProcess); FreeLibrary(hNtdllDll); return TRUE; } // OpenProcess打开高权限的进程需要提权 BOOL EnbalePrivileges(HANDLE hProcess, const char* pszPrivilegesName) { HANDLE hToken = NULL; LUID luidValue = { 0 }; TOKEN_PRIVILEGES tokenPrivileges = { 0 }; BOOL bRet = FALSE; DWORD dwRet = 0; // 打开进程令牌并获取进程令牌句柄 bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken); if (FALSE == bRet) { printf("OpenProcessToken"); return FALSE; } // 获取本地系统的 pszPrivilegesName 特权的LUID值 bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue); if (FALSE == bRet){ printf("LookupPrivilegeValue"); return FALSE; } // 设置提升权限信息 tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Luid = luidValue; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 提升进程令牌访问权限 bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL); if (FALSE == bRet){ printf("AdjustTokenPrivileges"); return FALSE; } else{ // 根据错误码判断是否特权都设置成功 dwRet = ::GetLastError(); if (ERROR_SUCCESS == dwRet){ printf("SUCCESS!!"); return TRUE; } else if (ERROR_NOT_ALL_ASSIGNED == dwRet){ printf("ERROR_NOT_ALL_ASSIGNED"); return FALSE; } } return FALSE; } int main() { HANDLE hProcess = GetCurrentProcess(); EnbalePrivileges(hProcess, SE_DEBUG_NAME); const char* dllPath = "E:\\Dll1.dll"; ZwCreateThreadExInjectDLL(2940, dllPath); return 0; }
0x05 踩坑记录
1、如果注入到x64程序,最好exe、dll都编译成x64
2、注入dll需要与注入进程的字符集相同,vs2019可以在项目属性->高级处选择字符集(被这里坑了好久,普通session层可以注入,session0注入不了,查了好久,最后一个大佬说字符集要相同,后面将dll、exe字符集改成多字符集注入成功了)