前言
在前段时间使用易语言hook的方式,实现了《11月的阿卡迪亚》的部分汉化,当时只是抱着试一试的心态,不过既然成功了,就当然想要深入的理解和研究。
最近又发现了个有趣的エロ游戏,于是乎我又打算尝试汉化,不过机会渺茫,没有搞懂资源结构,但是修改程序使其支持中文显示,应该还是可以的。
然后在尝试Hook某些API的时候,程序崩溃了,研究了整整一天才发现原因,So,写篇文章记录下来。
过程
放弃易语言
于是乎使用OllyDbg分析了一波api,发现这次的程序创建字体使用的是CreateFontIndirectA,传入参数是一个结构体,这d*g sh*t 易语言涉及到结构体的参数就不好处理,由于并不打算再深入研究易语言,So 我花了两天时间学习了C++。
使用C++ & MHook
虽然搞懂了C++的基本使用,不过我还不打算直接就干新的程序,首先参考之前汉化**《11月的阿卡迪亚》做一个C++ Ver的Hooker替代易语言Ver的Hooker**,了解了一番C++ APIHook后,我选择了使用MHook库,方便。
传统Hook & MHook的区别
- 传统Hook
调用被Hook的API时一般分为以下几步,比较繁琐。
- 调用Hooked API
- 跳转到Hook Func
- 暂停Hook
- 修改参数并调用True Func
- 恢复Hook
- 返回结果
- MHook
使用MHook可以简化为以下几步~~ 帮你封装好了 ~~,比较简单,完善。
- 调用Hooked API
- 跳转到Hook Func
- 修改参数并调用True Func
- 返回结果
- 注意:在Hook Func里不能调用Hooked API,必须通过True Func来操作原API,否则会崩溃(死循环了)。
开始Hook
操作环境
系统:Windows 7
软件:
Clion & MinGW(CMake),
MHook(Source Code),
LordPE
修改MHook
由于MHook原来是个VC++的project,我所使用的编译环境是CMake(好像是gcc),某些VC++特性不支持,比如没有某些头文件。
经过一番观察,还好MHook用到VC++的部分都是些debug用的输出内容,将其全部去掉即可~~,出了问题又不会修~~。还有一些基础类型不存在,不过有替代的选项,全部换掉即可。
尝试Hook
首先Hook的是,KERNEL32-GetSystemDefaultLangID,很轻松的Hook成功了。
于是乎,我傻逼傻逼的以为Hook就这么简单,一口气把之前的所有API的Hook Code都写好了,然后便是噩梦的开始。
莫名崩溃
当时我Hook的API有以下:
KERNEL32-GetSystemDefaultLangID
KERNEL32-MultiByteToWideChar
USER32-MessageBoxA
GDI32-CreateFontA
当我Hook KERNEL32-MultiByteToWideChar 和 GDI32-CreateFontA的时候,程序是绝对崩溃的,但我Hook KERNEL32-GetSystemDefaultLangID 和 USER32-MessageBoxA是正常的。
这让我很不解,为什么有些API正常,有些就不正常。于是我只Hook KERNEL32来进行测试。
一开始我以为是Hook时传值给API的方式不对,比如MultiByteToWideChar,返回的是转换后的长度,传入的参数lpWideCharStr会被改变,我觉得是**引用(&)还有指针(*)**的问题。
于是乎我折腾了半天,无果~~(还tm换回了传统Hook)~~。
发现问题
既然换回传统Hook还不行,我已经在怀疑人生了,然后我去翻阅了一遍原来易语言Ver的code,发现:
我*****,,而是用更传统的方式。才想起来那个程序不是以C的方式调用API(之前也因此崩溃过)
参考文章后发现,原来Win32 Api都是使用**__stdcall的方式调用,我写的Hook Func(没有标记调用方式)默认是以__cdecl**的方式调用的。
1._stdcall调用
_stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。
2._cdecl调用
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cdecl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
3._fastcall调用
_fastcall调用较快,它通过CPU内部寄存器传递参数。
按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。
解决方法
在Hook Func前面加上**__stdcall**标记即可。
__stdcall int KERNEL32::hookMultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int cchMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
) {
CodePage = 0x3a8;
return KERNEL32::trueMultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cchMultiByte, lpWideCharStr,cchWideChar);
}