如何捕捉键盘中英文输入

      最近在完成一个项目,需要捕捉用户的输入,传统的Keylogger网上资料较多,但捕捉的是按键输入,对于大量的中文输入,单纯键盘按键输入没有什么意义。本文说明如何实现中英文的输入捕捉。

 

  一、实现思路

            通过Windows钩子,截获每个应用程序的输入消息,获取输入内容。英文输入捕捉WM_CHAR消息,中文输入则铺捉WM_IME_ENDCOMPOSITION消息,再通过函数ImmGetCompositionStringW获取输入的内容。有了这个两个消息,就可以实现中英文输入的捕捉了。虽然思路很简单,但涉及到不少windows操作系统的概念,有不少坑。

 

二、实现中碰到的坑

(1)关于Windows hook,它不是执行在调用者的进程中,而是捕捉目标的进程中。

       windows hook是一个比较神奇的机制,设置windows hook看起来很简单:

 

HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);

 

设置一个回调函数,系统自动通过回调将捕捉到的windwos 消息传递给回调函数。

 

这里有一个大坑:按照我们以往使用回调函数的理解,回调会直接在调用进程中执行,但是windows hook不是,它的含义是把回调函数所在的dll注入到各个进程中,在各个进程中调用这个函数。如:捕捉word的中英输入时,该回调函数在word的进程中执行。而不是你的捕捉程序所在的进程中执行。因此调试主程序时,看不到回调被执行。需要调试word进程,才能看到回调函数被执行。

 

(2)如何做进程之间通信:dll共享段

      既然回调函数在不同的进程中执行,那捕捉的内容怎么收集? 所以要做进程间通信,把捕捉的信息都发送到主应用程序。进程通信主要有Socket,管道,windows消息,文件映射等等。这个dl必须是Native的(原因后面解释)用C++做,实现这些对我来说太复杂。但天无绝人之路,windows dll有个神奇的机制,可以在各个进程之间共享内存,叫共享数据段(data_seg),在这个段中的变量在各个进程中都可以访问。类似以下这种方式:

 

     //这个段里的变量都必须初始化,而且编译后直接占用dll的文件空间。
      #pragma data_seg("Shared")

      int  _inputBufferCount=0;
      WCHAR _inputBuffer[2]={0,0}    //根据实际需要分配大小

     #pragma data_seg()
     //RWS表示这个段可以读,写,共享。
     #pragma comment(linker,"/section:Shared,rws")

 

有了这个机制以后,回调函数把结果都放到这个共享段中,而主程序只要定时从该共享变量中读取捕捉信息即可。

 

(3)DotNet怎么调用系统钩子

      这个项目是用DotNet实现的,原以为在DotNet调用SetWindowsHookEx即可实现,但经过测试,只有鼠标和键盘的钩子可以用DotNet实现,这两个钩子的实现机制和其他钩子不同,它不需要将dll注入到其他进程中。而其他钩子都不能将回调函数放入DotNet的dll中。原因是DotNet的函数地址是一个动态生成的地址,注入其他进程时,其他进程无法找到这个动态的地址。(估计其他进程没有建立DotNet环境,连调用都没法调用)

     所以实现的方式是:用C++做一个捕捉的DLL,捕捉的结果放在共享段中,对DotNet提供一个获取捕捉结果的函数,DotNet程序定时调用。

 

(4)64位系统中怎么扑捉32位程序的输入

在windows中,32位的进程只能调用32位的dll,同理64位只能调用64位的dll。windwos hook也一样,如果生成的是32位的DLL,那么该dll只能注入到32位的进程中。64位的dll无法注入到32位进程中。

又一个大坑。在64位系统中,如何同时扑捉32位和64位的程序。想只实现一种,但发现Office系列是32位的,浏览器都是64位的,两种都很重要。

解决办法:同时编译一个64位和32位的dll。但是我的主程序要么是64位的,要么是32位的,怎么同时调用?所以必须生成一个32位的进程,负责调用32位dll,生成一个64位进程,负责调用64位dll.这两个进程通过网络接口供主程序访问,快疯了。(实际上我实现的时候,再主程序中判断本进程是32位还是64位,再另外启动一个不同位数的进程负责调用dll)

 

(5)windows hook 只能监视调用进程所在的那个用户的进程。

      SetWindowsHookEx只能将dll注入到调用者所在用户(Session)的进程。啥意思,如果一个windows中有两个登录用户,你在A用户启动了钩子,那么只能铺捉到A用户上打开的程序,切换到B用户后(A用户保持登录状态),B用户的任何程序的消息都不会被铺捉。所以监视程序不能放在windows 服务中,windwos 服务用的是单独的用户。进过测试,也不能放在.net remoting的调用中(估计用的也不是当前用户)。

 

三、实现代码

 (1)设置共享段

 

 #pragma data_seg("Shared")

//共享数据端,用于保存各个注入进程中捕捉的输入数据

//输入中字符的个数(不是字节数)
int  _inputBufferCount=0;
//监视输入得到的结果,采用UNICODE-16编码,每个字符两个字节
//这里数组有多大,就要初始化多大,我的实现开了2000个字符,满屏幕//的0,不要偷懒,多大的数组,就初始化多少,否则共享会失效。
WCHAR _inputBuffer[10]={0,0,0,0,0,0,0,0,0,0}

#pragma data_seg()
#pragma comment(linker,"/section:Shared,rws")
 

(2)设置wiindows 钩子

//开始钩子,如果该进程内已经启动钩子,那么直接返回TRUE
//如果有多个进程调用了该钩子,那么返回值会有多份相同的。调用进程需要保证调用的唯一性。

 

BOOL __stdcall StartHook()
{
//已经开始Hook了,不用重新调用,调用多次,一个消息就会回调多次
if(hkKey!=NULL)
      return TRUE;
InitShareData();
hkKey = SetWindowsHookEx(WH_GETMESSAGE,procCharMsg,hInstHookDll,0);
return hkKey!=NULL;
}

 

 

(3)获取中文输入

if(msg->message==WM_IME_ENDCOMPOSITION) //we handle only WM_CHAR messages
{
//从输入法中获取字符串,如果不是ansi字符,那么放入输入区,如果是ansi字符,则丢弃,在WM_CHAR中获取.
HIMC hIMC=ImmGetContext(msg->hwnd);//获取HIMC
if (hIMC==NULL)

        return;
}


//读取中文输入法,读取的数据为unicode-16编码数据
WCHAR  buffer[2000];
LONG size=ImmGetCompositionStringW(hIMC,GCS_RESULTSTR,buffer,sizeof(buffer));
  //数据放到共享段中
  AppendInput(buffer,size);
  ImmReleaseContext(msg->hwnd,hIMC);
}

(4)获取英文输入

if(msg->message==WM_CHAR)//we handle only WM_CHAR messages

{

            //只读取ansii码,不读取中文

WCHAR charCode=(WCHAR)msg->wParam;

if(!IsVisibleAnsiChar(charCode))

return;

 

//如果有控制字符的也不需要,如"Control,alt键等"

SHORT  controlState=GetKeyState(VK_CONTROL);

if((controlState&0xff00)!=0)

return;

 

 

SHORT  altState=GetKeyState(VK_MENU);

if((altState&0xff00)!=0)

return;

 

 //放到缓存区

AppendInputByChar(charCode);

 

}

 

四、结论

      通过windows hook的方式可以铺捉到中英文输入,但是不同应用程序和不同输入法组合,产生的windwos 事件不同,有的中文输入会同时产生WM_IME_ENDCOMPOSITION和WM_CHAR,有的只有WM_IME_ENDCOMPOSITION,有的这两个事件都没有(微软的输入法)。但大部分情况下,通过WM_IME_ENDCOMPOSITION铺捉中文(如果纯英文则丢弃),通过WM_CHAR铺捉英文(如果有中文则丢弃),能够很好的应对各种输入法和程序组合。如果有更好的实现方法,请告知,要源代码的:Cougars@yeah.net,很急的话,qq:747754571。以下是针对不同程序和输入法的一个测试结果:

 

 

 

应用程序输入法中文输入英文输入输入法下的英文输入
Visual StudiaosougouWM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
qqWM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
微软WM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
windows wordsougouWM_IME_ENDCOMPOSITIONWM_CHAR消息WM_IME_ENDCOMPOSITION
QQ输入法WM_IME_ENDCOMPOSITIONWM_CHAR消息WM_IME_ENDCOMPOSITION
微软只有kedown消息和Notify消息WM_CHAR消息只有kedown消息
IEsougouWM_IME_ENDCOMPOSITIONWM_CHAR消息WM_IME_ENDCOMPOSITION
微软只有kedown消息和Notify消息WM_CHAR消息只有kedown消息和Notify消息
QQ WM_IME_ENDCOMPOSITION,
结束时有个WM_CHAR的空格
WM_CHAR消息WM_IME_ENDCOMPOSITION,
结束时有个WM_CHAR的空格
EXCELQQWM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
sougouWM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
微软WM_IME_ENDCOMPOSITIONWM_CHAR消息WM_IME_ENDCOMPOSITION
普通.net程序QQWM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
sougouWM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息
微软WM_IME_ENDCOMPOSITION
+WM_CHAR消息
WM_CHAR消息WM_IME_ENDCOMPOSITION
+WM_CHAR消息

 

         

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值