淡淡哇 发表于 2020-8-20 17:58

windows输入法注入原理入门

本帖最后由 淡淡哇 于 2020-8-20 23:38 编辑


[*]前言
[*]输入法简介
[*]输入法安装
[*]输入法设置
[*]输入法注入
[*]输入法卸载
[*]参考资料


https://static.52pojie.cn/static/image/hrline/1.gif
前言
一直我都没有认真学过输入法注入,直到最近有时间想学习一下,才发现相关资料其实还是有一定的缺乏的,我参考了部分资料,以个人的理解写了这篇文章,由于本人编程基础较弱,可能与实际存在一定的出入
在这里感谢李恒道的帮助
https://static.52pojie.cn/static/image/hrline/1.gif
输入法简介
windows下输入法一般分为两种,一种是外挂式,一种是ime式
外挂式一般是通过键盘钩子对输入进行拦截,然后发送给对应的窗口
ime式则是通过系统提供的ime框架的基础上实现输入法,虽然叫ime,但是ime实际上是dll

ime调用流程:
键盘事件                应用程序
↓                   ↓
   Windows的USER.EXE
         ↓ ↑
       IME管理(应该是imm32.dll)
         ↓ ↑
      输入法
既然是在系统提供的ime框架上进行开发,框架就有一定的标准接口,标准接口的函数如下

    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      //选择汉字窗口注册函数

虽然看着很多,但是我们并不需要了解所有,只需要明白一些关键的函数即可明白输入法注入
https://static.52pojie.cn/static/image/hrline/1.gif
输入法安装
我们想要使用输入法,第一步是将输入法安装到系统里,把ime文件写入目录C:\WINDOWS\system32后调用api ImmInstallIME

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

这样就会成功安装输入法了
https://static.52pojie.cn/static/image/hrline/1.gif
输入法设置
接下来我们使用IMESetPubString对输入法设置注入的dll路径(这里的api是imedllhost09.ime文件内的)
输入法的源码如下

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;
}

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

从这里开始我们来理解一下调用逻辑
因为程序调用了imedllhost09.ime的IMESetPubString
所以会使用kernel32.LoadLibraryA加载模块,然后调用kernel32.GetProcAddress获取IMESetPubString,也就是说我们一般是第一个加载了imedllhost09.ime输入法模块的。
接下来我们捋一捋输入法内部的流程

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
   switch(fdwReason)
    {
      case DLL_PROCESS_ATTACH:
                  if(!ImeClass_Register(hinstDLL)) return FALSE;   // DLL加载时注册必须的UI基本窗口类
                  MyLoadCilentDLLFun();
                  break;
          case DLL_THREAD_ATTACH:
               break;
          case DLL_THREAD_DETACH:
               break;
      case DLL_PROCESS_DETACH:
                  ImeClass_Unregister(hinstDLL);// DLL退出时注销注册的窗口类
                  if (CilentDLL!=NULL && OnloadDllWhenExit==0)
                  {
                        FreeLibrary(CilentDLL);    // 输入法退出时卸载客户DLL
                  }
      break;
      default:
      break;
    }
      return true;
}

if(!ImeClass_Register(hinstDLL)) return FALSE;是注册了一个基本的窗口类
然后调用了函数MyLoadCilentDLLFun();
接下来我们看看MyLoadCilentDLLFun();

void MyLoadCilentDLLFun()
{
      if (CilentDLL==NULL)
      {
                  if (lstrlen(g_IMEDLLString)>0)
                  {
                        CilentDLL=LoadLibrary(g_IMEDLLString);   // 在输入法加载时同时加载客户DLL
                        if (CilentDLL!=NULL)
                        {
                                  // 如果存在,则调用客户DLL指定名称的回调函数
                                  RunDllCallBackX=(RUNDLLHOSTCALLBACK)GetProcAddress(CilentDLL,"RunDllHostCallBack");
                                  if (RunDllCallBackX!=NULL)
                                  {
                                          RunDllCallBackX(CallBackData1,CallBackData2,CallBackData3);
                                  }
                        }
                  }
      }
}

首先判断CilentDLL是否为空,因为我们刚初始化肯定是空的,然后调用获取g_IMEDLLString字符串长度,判断是否大于0,如果大于0再执行注入,这里我们刚初始化的,肯定为空,所以执行逻辑结束
执行完dllmain入口函数后,会紧接着执行ImeInquire(),ImeInquire是输入法初始化过程,这里我们不再叙述

输入法内部的流程我们大概梳理完了,接下来我们来看IMESetPubString函数

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;
}

这里我们可以看到IMESetPubString是经过了一些简单的赋值,到这里我们的输入法设置已经结束
https://static.52pojie.cn/static/image/hrline/1.gif
输入法注入
注入到目标程序使用的是SendMessageA (WinHwnd, 80, 1, ImeHwnd)
WinHwnd是目标程序窗口句柄
ImeHwnd是输入法句柄
这里的80,1我没有查阅到具体的资料,认为是通过spy++抓取的
目标程序加载了imedllhost09.ime后同样会执行dllmain函数
与之前不同的是这次g_IMEDLLString是已经被赋值的了

为什么在自己程序里设置g_IMEDLLString,而在其他程序也可以生效呢?


#pragma data_seg("mysechx")
DWORD CallBackData1=0;
DWORD CallBackData2=0;
DWORD CallBackData3=0;
DWORD OnloadDllWhenExit=0;    // 当输入法退出时是否卸载客户DLL0-是,1-否
DWORD LoadNextWhenActive=0;    // 当本输入法激活时,是否自动打开下一个输入法 0-否,1-是
char g_IMEDLLString="";
#pragma data_seg()

#pragma data_seg()用于dll中,在dll中定义一个共享的,有名字的数据段
这个数据段的变量可以被多个进程共享,否则多个进程之间无法共享dll的变量
注意:这里的共享数据必须进行初始化
这样我们就实现了跨进程的数据通信,接下来我们继续看MyLoadCilentDLLFun函数

void MyLoadCilentDLLFun()
{
      if (CilentDLL==NULL)
      {
                  if (lstrlen(g_IMEDLLString)>0)
                  {
                        CilentDLL=LoadLibrary(g_IMEDLLString);   // 在输入法加载时同时加载客户DLL
                        if (CilentDLL!=NULL)
                        {
                                  // 如果存在,则调用客户DLL指定名称的回调函数
                                  RunDllCallBackX=(RUNDLLHOSTCALLBACK)GetProcAddress(CilentDLL,"RunDllHostCallBack");
                                  if (RunDllCallBackX!=NULL)
                                  {
                                          RunDllCallBackX(CallBackData1,CallBackData2,CallBackData3);
                                  }
                        }
                  }
      }
}
此时CilentDLL依然为null,但g_IMEDLLString已经在IMESetPubString被赋值
接下来会调用LoadLibrary加载注入dll
然后调用GetProcAddress获取注入dll的RunDllHostCallBack回调函数
并且调用回调函数传入CallBackData1,CallBackData2,CallBackData3三个数据
至此我们的输入法注入彻底结束了
https://static.52pojie.cn/static/image/hrline/1.gif
输入法卸载
停止注入调用的函数是IMEClearPubString,让我们看一下输入法的IMEClearPubString函数

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

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

依然是对变量进行简单的赋值

接下来卸载输入法
删除以下注册表内的输入法标号(此处来自精易模块)

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

接下来删除对应的注册表就可以了

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

然后调用UnloadKeyboardLayout卸载输入法
并且删除imedllhost09.ime以及对应的缓存文件


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


https://static.52pojie.cn/static/image/hrline/1.gif
参考资料
参考源码https://www.52pojie.cn/forum.php?mod=viewthread&tid=38537&highlight=%CA%E4%C8%EB%B7%A8%D7%A2%C8%EB
这里感谢lzq大神

IME输入法编程心得 https://www.cnblogs.com/freedomshe/archive/2012/11/30/ime_learning.htmlIME输入法编程溯源 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/

https://static.52pojie.cn/static/image/hrline/1.gif



IBinary 发表于 2020-8-21 16:21

顶一下.2楼不要说什么检测啥的. 毕竟你学习的是注入.是技术.不是让你来 做外挂的. 任何技术一旦分享出来都会面临着对抗技术的升级. 输入法当时很多主流的游戏都检测不到.或者没办法检测. 但是公开了也就这样了. 学一下技术最好.了解下原理.

淡淡哇 发表于 2020-8-20 23:46

自己顶一下我自己!

xuxunyue 发表于 2020-8-21 16:11

输入法注入容易被检测到

endriver 发表于 2020-8-21 16:35

关键是要了解各种系统函数的用法

无秽之鸦 发表于 2020-8-21 18:48

这注入不知道咋样

netspirit 发表于 2020-8-21 20:59

IBinary 发表于 2020-8-21 16:21
顶一下.2楼不要说什么检测啥的. 毕竟你学习的是注入.是技术.不是让你来 做外挂的. 任何技术一旦分享出来都 ...

现在就别说注入了 就是真的输入法游戏也用不了啊

REK_滑稽 发表于 2020-8-21 21:03

支持一下

iwannaufly 发表于 2020-8-22 04:07

厉害,我一直直接用的类似精益模块的文本_投递

花好s月圆 发表于 2020-8-22 06:38

xuxunyue 发表于 2020-8-21 16:11
输入法注入容易被检测到

直接注入到游戏,更容易被检测。
页: [1] 2 3
查看完整版本: windows输入法注入原理入门