前言

在前段时间使用易语言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时一般分为以下几步,比较繁琐。

  1. 调用Hooked API
  2. 跳转到Hook Func
  3. 暂停Hook
  4. 修改参数并调用True Func
  5. 恢复Hook
  6. 返回结果
  • MHook

使用MHook可以简化为以下几步~~ 帮你封装好了 ~~,比较简单,完善。

  1. 调用Hooked API
  2. 跳转到Hook Func
  3. 修改参数并调用True Func
  4. 返回结果
  • 注意:在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-MultiByteToWideCharGDI32-CreateFontA的时候,程序是绝对崩溃的,但我Hook KERNEL32-GetSystemDefaultLangIDUSER32-MessageBoxA是正常的。

这让我很不解,为什么有些API正常,有些就不正常。于是我只Hook KERNEL32来进行测试。

一开始我以为是Hook时传值给API的方式不对,比如MultiByteToWideChar,返回的是转换后的长度,传入的参数lpWideCharStr会被改变,我觉得是**引用(&)还有指针(*)**的问题。

于是乎我折腾了半天,无果~~(还tm换回了传统Hook)~~。

发现问题

既然换回传统Hook还不行,我已经在怀疑人生了,然后我去翻阅了一遍原来易语言Ver的code,发现:

易语言版本Hooker

我*****,才想起来那个程序不是以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);
}