2020年8月

为了防止原数据丢失,得此做一个备份
原文地址:https://blog.csdn.net/pkfish/article/details/7339890 作者:pkfish

·win下的输入法编程方法

    在win下编写输入法主要可以采用外挂式和IME方式两种:

    ·外挂式的实现核心是通过键盘钩子对输入进行拦截,再把要输出的内容传送给处于活动状态的编辑窗口。

    ·IME方式则是使用系统提供的IME(Input Method Editor)接口实现输入法。

    头一种方法自己并未接触过,但使用钩子技术在兼容性上来说总是有点不足的(遇上防钩子的程序,输入法就没辙了),再者,把要输出的内容传送给处于活动状态的输入焦点处并不总能成功(对win下的api了解不多,这里或许有误)。既然系统已经为我们编写输入法专门弄了个IME出来,那当然还是直接用系统的好些啦。

·IME介绍

    IME的实现原理可用下图简单的表述:


键盘事件  应用程序

↓     ↑

Windows的USER.EXE

↓ ↑

输入法管理器(IME)

↓ ↑

输入法


    当当前活动窗口开启了输入法后,键盘事件就会通过系统进程USER.exe转交给IME处理,IME再转告给当前输入法,输入法处理完后把处理结果告知IME,IME根据这个处理结果决定反馈给USER.exe,然后再反馈给当前应用程序的活动窗口。

    基于IME开发的输入法文件后缀为“ime”,但实际上是一个dll文件。这个dll文件类似于IME的一个插件存在,实现了IME要求的十几个接口函数。实际上,当IME收到USER.exe发来的键盘事件时,IME就会调用当前启动的输入法中某些IME接口函数,根据这些函数的返回值,IME可以确定输入法是否打算处理当前的键盘输入(选择不处理的情况下该键盘事件将会送去给应用程序自己去处理,否则应用程序不会收到该键盘事件),或把输入法要求输出的内容输出到应用程序中。

·系统对输入法信息的保存

    IME输入法的信息保存在注册表中,位置有两个:



    ·HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Keyboard Layouts



    这里保存了所有当前系统已安装的输入法的基本信息,这里要特别说明一下:已安装的输入法并不仅仅是指在语言栏里能直接看到,能用热键切换的输入法,还包括安装了但未在语言栏中启动的那些,这些未启用的输入法可以在语言栏的设置->按[添加]按钮弹出的窗口中找到。

    在这个注册表项下是一堆以英文和数字组成的8位字符串命名的子项(实际应该理解为一个8位的16进制数,为输入法的键盘布局唯一标识号),这里每一个子项代表的就是一个已安装的输入法。而这些子项的字符串的后4位代表了该输入法所属的语言,这个值具体的意义可从“HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Keyboard Layout\\DosKeybCodes”项里面的值里查到,而前4位则仅表示一个安装序号,以保证每个输入法项的这个8位字符串都是唯一的,每当有新的输入法通过系统提供的方法安装进来,系统就会找出与要安装的输入法同一语言的输入法中最大使用到的安装序号,新安装的输入法则在这个安装序号基础上+1,如果安装是完全通过自行写入注册表的方式完成的,则只要保证字符串后4位值满足要求且整个8位字符串值惟一也行,不过还是建议使用系统提供的方法安装(具体操作文章稍后的地方有讲)。

    举个例子,假如当前系统刚通过系统提供的方法安装了一个新输入法,其安装好后的输入法项为“00060804”,则根据后4位“0804”可以查到,这个输入法的语言是属于中文的,而前4位“0006”则为其安装序号,假设现在有一个新的中文输入法要通过系统提供的方法安装进来,则这个新输入法安装完后所分配的项应该为“00070804”。

在对于非系统的输入法项里面都写有三个必定存在的键:

Ime File:该输入法对应的ime文件文件名。系统从system32目录(win9x下为相对于system目录)下读入该文件;

Layout File:键盘布局文件,一般指向某个系统文件(自己也没搞清楚是怎么回事[-.-]);

Layout Text:在语言栏设置中选择要操作的输入法(启用/取消启用)时看到的输入法名称。

    另外也有输入法项里存在Layout Display Name和Layout Id这些键,但没研究过其意义。



    ·HKEY_CURRENT_USER\\Keyboard Layout\\Preload



    这个位置保存的是当前用户启用的输入法的列表,也即当前用户可在语言栏中切换到的输入法都可以在这里看到。每个启用的输入法占一个键,其键名代表了该输入法的启用顺序,值内容则为该输入法安装后的8位字符串标识。

  • 引言
  • 输入法简介
  • 输入法安装
  • 输入法初始化
  • 输入法注入
  • 输入法激活
  • 输入法卸载

    • 输入法标号的获取

引言

国内输入法相关的资料相对缺乏,大部分都是抄来抄去,我参考了一部分源码和代码总结出来了这篇文章,可能依然有一定的错误(由于本人薄弱的编程功底)
推荐一部分相关的资料
IME输入:https://www.jianshu.com/p/ba7ef776112e
IME输入:https://www.cnblogs.com/freedomshe/archive/2012/11/30/ime_learning.html
第二个好像是正版
IME输入法编程溯源https://www.cnblogs.com/freedomshe/archive/2012/11/13/ime-resources.html
加密与解密第四版:https://detail.tmall.com/item.htm?spm=a230r.1.14.16.1e7d38f5rT1yMd&id=580607194609&ns=1&abbucket=10
易语言输入法注入:http://www.511yj.com/eyuyan-zr-srf.html
dll技术之输入法注入:https://blog.csdn.net/qq446569365/article/details/71155557
精益模块注入法注入:http://ec.125.la/

输入法简介

输入法注入利用的是ime文件,ime是一个符合windows平台输入法接口规范的dll
有以下标准接口

IMESetPubString
IMEClearPubString
ImeConversionList
ImeConfigure
ImeDestroy
ImeEscape
ImeInquire
ImeProcessKey
ImeSelect
ImeSetActiveContext
ImeSetCompositionString
ImeToAsciiEx
NotifyIME
ImeRegisterWord
ImeUnregisterWord
ImeGetRegisterWordStyle
ImeEnumRegisterWord
UIWndProc
StatusWndProc
CompWndProc
CandWndProc


在此贴上网上的功能解释段

在CODE上查看代码片派生到我的代码片

    ImeConversionList           //将字符串或字符转换成目标字串    
    ImeConfigure                //配置当前ime参数函数    
    ImeDestroy                  //退出当前使用的IME    
    ImeEscape                   //应用软件访问输入法的接口函数    
    ImeInquire                  //启动并初始化当前ime输入法    
    ImeProcessKey               //ime输入键盘事件管理函数    
    ImeSelect                   //启动当前的ime输入法    
    ImeSetActiveContext         //设置当前的输入处于活动状态    
    ImeSetCompositionString     //由应用程序设置输入法编码    
    ImeToAsciiEx                //将输入的键盘事件转换为汉字编码事件    
    NotifyIME                   //ime事件管理函数    

    ImeRegisterWord             //向输入法字典注册字符串    
    ImeUnregisterWord           //删除被注册的字符串    
    ImeGetRegisterWordStyle    
    ImeEnumRegisterWord    

    UIWndProc        //用户界面接口函数    
    StatusWndProc    //状态窗口注册函数    
    CompWndProc      //输入编码窗口注册函数    
    CandWndProc      //选择汉字窗口注册函数  

输入法安装

当目标进程切换输入法的时候imme32.dll(输入法管理dll)会加载对应ime模块

我们一般是调用ImmInstallIME进行安装输入法

ImmInstallIME
函数原型:
HKL ImmInstallIME(LPCTSTR lpszIMEFileName, LPCTSTR lpszLayoutText);
函数的两个参数分别为输入法IME文件的文件名和在控制面板的是输入法选项中显示的输入法名称。函数调用后将返回一个被安装输入法的输入法标识符(或称做输入法句柄)。
示例代码:
HKL hKL = ImmInstallIME("c:\\winwb86.ime", "王码五笔型输入法86版");

输入法初始化

输入法初始化过程调用的是ImeInquire,会在dllmain调用后第一个调用这个

BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption)
{
    // 输入法初始化过程
    lpIMEInfo->dwPrivateDataSize = 0; //系统根据它为INPUTCONTEXT.hPrivate分配空间

    lpIMEInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST | 
                             IME_PROP_IGNORE_UPKEYS |
                             IME_PROP_END_UNLOAD; 

    lpIMEInfo->fdwConversionCaps = IME_CMODE_FULLSHAPE |
                                IME_CMODE_NATIVE;

    lpIMEInfo->fdwSentenceCaps = IME_SMODE_NONE;
    lpIMEInfo->fdwUICaps = UI_CAP_2700;

    lpIMEInfo->fdwSCSCaps = 0;

    lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION;

    _tcscpy(lpszUIClass,CLSNAME_UI);  // 注意该输入法基本窗口类必须注册,否则输入法不能正常运行

    return TRUE;
}

输入法注入

然后向输入法注入dll,使用的是IMESetPubString
tmpStr是dll的路径
UnloadDLL是输入法退出的时候是否卸载dll 0代表是 1代表否
loadNextIme切换目标输入法的时候是否直接切换到下一个输入法 0代表否 1代表是
DllData1 数据1
DllData2 数据2
DllData3 数据3

int WINAPI IMESetPubString(LPCTSTR tmpStr,DWORD UnloadDLL,DWORD loadNextIme,DWORD DllData1,DWORD DllData2,DWORD DllData3)
{
    CallBackData1=DllData1;
    CallBackData2=DllData2;
    CallBackData3=DllData3;
    OnloadDllWhenExit=UnloadDLL;
    LoadNextWhenActive=loadNextIme;

    memset(g_IMEDLLString,0,802);
    if (lstrlen(tmpStr)>800)
    {
        lstrcpyn(g_IMEDLLString,tmpStr,800);
    }
    else
    {
        lstrcpy(g_IMEDLLString,tmpStr);
    }
    return 1;
}

输入法激活

激活的时候调用SendMessageA (WinHwnd, 80, 1, ImeHwnd)
WinHwnd是对应窗口的句柄
ImwHwnd是输入法的句柄
这里目测应该是用过spy抓取消息获取的

输入法停止注入

停止注入的时候调用IMEClearPubString
附上IMEClearPubString的源码

int WINAPI IMEClearPubString()
{
    CallBackData1=0;
    CallBackData2=0;
    CallBackData3=0;
    OnloadDllWhenExit=0;
    LoadNextWhenActive=0;

    memset(g_IMEDLLString,0,802);
    return 1;
}

输入法卸载

卸载输入法的时候首先要获取输入法的标号

输入法标号的获取

输入法标号的获取
首先调用api GetKeyboardLayoutList来获取输入法总数
然后循环调用api LoadKeyboardLayoutA获取每个输入法的句柄
通过判断输入法的句柄是否相等来获得输入法标号

删除以下注三个册表内的输入法标号

“Keyboard Layout\Preload\”
“SYSTEM\CurrentControlSet\Control\Keyboard Layouts\”
“S-1-5-21-1060284298-606747145-682003330-500\Keyboard Layout\Preload”

然后调用api UnloadKeyboardLayout卸载键盘布局
并且删除目录下的对应ime文件以及缓存文件

缓存文件的路径以及名称在注册表的“SYSTEM\CurrentControlSet\Control\Keyboard Layouts\”下
输入法的表示路径在注册表的 “Keyboard Layout\Preload\”下

线程注入可以使A程序操控B程度加载特定的dll

我们使用线程注入来把dll注入到目标进程主要需要以下api

OpenProcess 打开进程
VirtualAllocEx 分配内存
WriteProcessMemory 对内存写入数据
GetModuleHandleA 获取模块的地址(我们需要调用LoadLibraryA来加载dll,因为LoadLibraryA在Kernel32.dll文件中,所以需要先获取Kernel32.dll地址)
GetProcAddress获取LoadLibraryA函数地址
附注:因为windows机制,低2G属于用户空间 高2G属于系统空间,所以在不同程序下获取的LoadLibraryA函数地址是相同的
CreateRemoteThread在目标程序中建立线程

建立线程后调用LoadLibraryA加载目标dll
目标dll启动dllMain来执行函数
在此贴出精益的dll注入源码(易语言版本)

.版本 2

局_进程ID = OpenProcess (2035711, 0, 目标进程ID)
长度 = 取文本长度 (要注入的DLL文件名)
局_内存值 = VirtualAllocEx (局_进程ID, 0, 长度, 4096, 4)
WriteProcessMemory_字节集 (局_进程ID, 局_内存值, 到字节集 (要注入的DLL文件名), 长度, 0)
局_函数地址 = GetProcAddress1 (GetModuleHandleA (“Kernel32”), “LoadLibraryA”)
局_线程ID = CreateRemoteThread (局_进程ID, 0, 0, 局_函数地址, 局_内存值, 0, 0)
.如果真 (局_线程ID = 0)
    返回 (假)
.如果真结束
WaitForSingleObject (局_线程ID, 4294967295)
VirtualFreeEx (局_进程ID, 局_内存值, 0, 32768)
CloseHandle (局_线程ID)
CloseHandle (局_进程ID)
返回 (真)

传统的HTTP协议存在一个巨大的缺陷
只能由客户端向服务器端发起请求
唯一的解决办法就是由客户端定时向服务端发送请求来进行获取信息

为了解决这个问题
开发了WebSocket、节省了宽带资源同时可以更有效的通信

WebSocket相对于HTTP协议最大的特点就是可以由客户端发向服务器端,也可以由服务器端向客户端发起通信

WebSocket是建立在TCP协议之上、与HTTP协议具有较好的兼容性,默认的端口跟HTTP相同,是80/443
WebSocket的握手截断采用的HTTP协议,不容易被屏蔽,同时能通过http代理服务器

客户端向服务器端连接时,发送HTTP的协议头会有附加信息,如Upgrade:WebSocket等

优点:
相对HTTP协议节省了大量的资源
服务器可以向客户端直接推送信息

缺点:
少部分浏览器不支持
同时客户端与服务器之间的网络节点可能会出现蜜汁错误,如直接拦截,导致双方通信失效