过年前打算入手一台手提,挑选对比了不少型号,终于买了一台HP541手提,主要是看上它价格比较低廉,不过有着还算靠谱的性能,特别是IO性能非常令人满意。验货时已经觉得它外表非常普通,而且快捷键非常少,很明显连触摸板的控制键都没有,不过那时对这个也不太在意,反正基本上都是用鼠标操作,安装完触摸板驱动后还把它禁用了,免得打字的时候拇指不小心碰到它导致误操作。
清明节放假三天回了三水,因为家里有无线路由器,因此可以休闲地坐在大厅里一边看电视一边把手提放在大腿上上网,不过比较苦闷的每次从电脑桌上拿到大厅里的时候都要先打开触摸板控制界面,然后将触摸板启用,之后才能将鼠标拔掉,否则的话就出现鼠标拔掉了但触摸板也用不了的尴尬场面。
虽然HP541的模具有局限,不过山人自有妙计,我想到可以用Windows的组合键来控制触摸板的开关。大部分的触摸板都是使用synaptics的驱动的,于是到http://www.synaptics.com找了 synaptics的sdk和samples。打开samples看了一下,只有com形式的调用,并非驱动级的调用。当时也没太在意就兴冲冲地建了一个mfc工程打算丰富一下它的功能,至少可以随windows启动、最小化时显示任务栏图标和通过组合键来控制它的开关吧。
花了半天的时间把工具写好并测试过没什么问题了,但却发现了一个致命的弱点,那就是务必要让SynTPEnh.exe(安装好synaptics驱动后随windows启动的进程)先运行,否则采用synaptics sdk里那几个samples的做法是无法初始化和加载synaptics的com指针的,用Ollydbg调试了一下SynTPEnh.exe,发现原来SynTPEnh.exe会在运行时通过LoadLibrary加载%Winroot%/System32目录里的Syntcom.dll的,看来这个才是正主。我的原意是用我写的这个工具来替代SynTPEnh.exe的,但现在如果要两个进程同时存在,这样多恶心啊。
现在星期一至星期五的晚上一般是逛街和散步,就算用电脑也只是玩一下游戏,因此一直到这个周六才继续搞。增加全局快捷键一般是用键盘钩子的,钩子是一定要通过dll来加载的。但怎样安装这个钩子呢?想了很多种方法,想过使用创建远程线程的方式将钩子注入到SynTPEnh.exe中,也想到过用注册表来注入,但都否定了,因为这些方法都新开一个进程或线程来安装钩子,最终想到修改SynTPEnh.exe,将安装钩子的代码插入到SynTPEnh.exe的某个地方里。
步骤如下:
1 建立一个名为HOOK的vc dll工程,dll导出一个函数InitializeHook,在此函数里先初始化synaptics的com指针,然后使用SetWindowsHookEx安装低级键盘钩子;在dll的退出函数里卸载钩子。
2 在钩子回调函数里检测是否设定的组合键(这里是Win键和加号键的组合),如果是的话就将触摸板禁用/启动。
3 由于要在SynTPEnh.exe的某处插入InitializeHook这个函数,因此要增加SynTPEnh.exe的空间,切记新开辟的空间要以DWORD对齐。还需要修改SynTPEnh.exe的导入表,将HOOK的InitializeHook导入到SynTPEnh.exe里,当然你也可以显式地通过LoadLibrary在SynTPEnh.exe里加载这个dll,不过那要写多几行代码了。这里建议使用Topo和Stud_PE这两个工具,不但可以极大地增加你对PE格式的了解,而且用起来非常方便。
4 使用反汇编工具,例如OllyDbg,将call dword ptr [5020F9]这个指令写到SynTPEnh.exe文件里的某个地方。一开始我打算修改SynTPEnh.exe的入口点,先执行call dword ptr [5020F9]这个指令,然后再跳回原来的入口,但发现这样行不通,因为此时SynCom.dll还没加载呢。经过一轮的调试逆向摸索,发现SynTPEnh.exe除了主线程外,还新开了3个线程和1个定时器,分别用作加载任务栏图标、加载驱动和响应用户的触摸使任务栏图标活动起来。我分别以CreateThread和Shell_NotifyIcon等Win32 API作断点观察了很久,将call dword ptr [5020F9]这个指令放到线程创建完毕或者图标创建完毕之后调用,但还是失败,总是无法初始化synaptics的com指针。最后出动到IDA Pro,将Call指令放到WinMain函数的消息泵前面,结果还是饮恨,代码如下:
sub_4310E0((int)&v18);
//Call指令放在这里
PostMessageW((HWND)*(&dword_4781C0 + 56), 0x47Fu, 0, 0);
while ( GetMessageW(&Msg, 0, 0, 0) )
{
if ( !*(&dword_4781C0 + 58) || !IsDialogMessageW((HWND)*(&dword_4781C0 + 58), &Msg) )
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
5 此路不通,而且我调试了半天也没发现到底是调用了SynCom.dll里那个函数来初始化驱动的。我想到一个比较折衷的方法,在钩子的回调函数里发现用户是按下了预设的组合键时(默认是Win键和加号键),先检测一下SynAPI的com指针实例化是否成功,如果还没实例化则先实例化。看代码:
void InitializeSynDevice()
{
if(bFoundDevice == FALSE)
{
CoInitialize(NULL);//这里是关键,应该将这行移到DLL的入口。
if( CoCreateInstance(_uuidof(SynAPI), 0,
CLSCTX_INPROC_SERVER, _uuidof(ISynAPI), (void **) &pAPI))
{
//com初始化失败
AfxMessageBox(_T("Fail to Initialize Com"));
return;
}
//初始化synapi
if ( pAPI->Initialize() == SYNE_FAIL)
{
AfxMessageBox(_T("Fail To Initialize SynApi"));
bFoundDevice = FALSE;
return;
}
long lHandle = -1;
//查找设备
if (pAPI->FindDevice(SE_ConnectionAny, SE_DeviceTouchPad, &lHandle) || pAPI->CreateDevice(lHandle, &pDevice))
{
AfxMessageBox(_T("Find Device is failing"));
bFoundDevice = FALSE;
return;
}
bFoundDevice = TRUE;
}
}结果令我大跌眼镜,竟然还是不行。每一次按Win键和加号组合键,总是提示:Fail to Initialize Com。看看时间,是时候跟老婆去产检了,回来后在楼下跟街坊打了一会麻将,虽然胡了几把,但感觉还是很不爽也很不甘心。于是跑回楼上继续捣鼓,突然灵光一闪:当SynAPI还没实例化的时候,每一次按下组合键都会调用一次:CoInitialize(NULL),是否由于多次调用CoInitialize(NULL)而导致Com失败呢?于是将CoInitialize(NULL)移到DLL的入口,果然成功了!下面附上使用Ollydbg修改SynTPEnh.exe的截图。