看官你可以下载了附件,然后用ida 6.0打开了对照我的分析来看东西。因为这篇是一边分析一边写的,6、7个小时写到最后面前边写了什么话都忘了,所以看得懂汇编的话,打开分析好的idb配合文章读,会流畅的多。
汉化TAK.rar (3.89 MB)
ida6.2 demo的下载地址, http://out5.hex-rays.com/files/idademo_windows62.exe
TAK中文版比TAK原版不一样出来的文件,计有
Kingdoms.exe
Kingdoms.dat
Kingdoms.icd
gtsp.dll
eng12.dot
chn12.dot
PlugIns\kingdoms.txt
PlugIns\HookKingdoms.dll
简单的用记事本打开看下这几个文件里的字符串,根据经验知道了一些信息,
Kingdoms.dat
Kingdoms.icd
也是可执行程序,因为它们是用"MZ"开头的。又看到了一个字符串C:\GTLog.txt,log很明显是运行的记录。运行一次后在GTlog.txt中知道了chn12.dot和eng12.dot原来是字体文件:
Type:一般信息,Content:调入英文字体文件:eng12.dot,Time:2012.1.25 20:55:49
Type:一般信息,Content:调入中文字体文件:chn12.dot,Time:2012.1.25 20:55:49
因为这几个程序非常的小,所以不从查找关键代码出发,直接从头开始看。先确定了这个新的Kingdoms.exe是delphi写的,所以用带有非常好的资源编辑功能的pexplorer工具看下Kingdoms.exe的RCDATA类型的资源,delphi的窗体等的信息都保存在这儿。发现只有一个名为运行TAK的窗口。
object FormRunKingdoms: TFormRunKingdoms
因为我以前研究过delphi程序编译后的形式,所以轻车路熟的先把IDA的delphi的sig(shift+F5快捷键里可以选择)用上识别下delphi库函数,然后翻了一遍delphi的初始化函数表,里面都是delphi库函数,又翻到delphi创建主Form的位置,
mov edx, off_43B598
call @TApplication@CreateForm$qqrp10TMetaClasspv ; TApplication::CreateForm(TMetaClass *,void *
知道这个off_43B598中保存的是TAppliction类的结构,delphi里叫类的结构为TMetaClass,意思是类的原型。
详细的结构要说完非常累赘,函数TApplication::CreateForm的功能主要是创建主Form的类的。就像c++中的构造函数一样,delphi也会调用一个叫做Forms::TCustomForm::TCustomForm的虚函数来初始化Form类本身,之后会在一个叫做Classes::InitInheritedComponent的函数中把这个Form的子窗口、子控件(定时器)之类都进行初始化。
当然,我们是不用一步步跟踪过去看它调用了哪个函数初始化的,delphi的form 的TMetaClass保存了我们需要关心各个结构。
delphi编程中这些主Form等类的虚函数们,也就是直接用一个地址来保存和调用的函数们,它们中通常是不会有用户编写的。delphi把用户编写的代码都会带上一个特定的结构,而且里面绝大部分都带着函数的名称。Form的构造函数不是程序员编写的,但却会在delphi给Form设计的构造函数中调用一个叫做TCustomForm::DoCreate的回调函数,也就是RCData中保存的OnCreate 响应函数,这也是用户代码中进行Form初始化的函数。
OnCreate = FormCreate
从主Form的TMetaClass结构我们可以找到它所有的用户自定义函数,OnCreate通常是一个重载的publick 函数,这种函数的列表保存在TMetaClass结构的0x18偏移处,也就是[[43B598]+0x18]处,其指向的是下面的结构
struct MethodTable
{
__int32 Counter;
struct _tagMethodInfo
{
__int16 MethodIndex;
PROCADDR MethodAddr;
ShortString MethodName;
} MethodInfo[0];
}
过去一看就找到OnCreate的回调函数地址是sub_43B7C0,函数43B7C0的作用是设置一个子空间的高和宽跟屏幕一样大,也就是汉化版TAK启动时的那个logo窗口。除此之外,我们更关注的是,
它还调用了一个跟设置控件没关系的函数,sub_43B71C,在里面调用到了原版TAK没有的DLL,gtsp中的二个函数,大意为下,
gtsp.InitGameServer
->运行Kingdoms.dat程序
->查找窗口"Kingdoms Class",存在的话等待继续查找
->查找不到窗口时,gtsp.FreeGameServer
->Forms::TApplication::Terminate(void),强制结束本程序的执行。
汉化版的Kingdoms.exe的功能到此为止。
Kingdoms.dat的功能事实上更简单,它负责创建一个名为GlobalSemKingdomsLauncherActive的互斥量,防止TAK运行多次。
接着构造命令行,最后运行Kingdoms.icd,命令行的格式,相当于在CMD窗口按照下面的格式来运行的,TAKPath 含义是TAK的目录
TAKPath\Kingdoms.icd TAKPath 运行起来Kingdoms.dat的命令行
事实上Kingdoms.icd 就是原始的TAK的主程序,直接运行它就可以了。但是汉化呢,我们的汉化在哪儿进行的?
除了前面的部分,就只有gtsp.dll中的二个函数还被执行了。
用反汇编器打开gtsp.dll,就会恍然大悟,原来是利用了win98的这个特性,但还没有确定,待会儿确认后再详细说。
win下的DLL程序有一个特点,它是被动执行的,主要功能都是在DLL的导出函数里,被其他程序调用时才执行。但是DLL也有一个初始化过程,也就是DllMain
gtsp.dll的DllMain很简单,在初始化时,也就是处理DLL_PROCESS_ATTACH的过程中,只获取了自己的目录,因为DLL比较特殊,除了在初始化过程中,往后要获得自己的目录会比较麻烦。以及设置了日志文件位置为c:/GTLog.txt,获取了系统的版本。
这儿只判断了win95和win98两种不同的系统类型。
创建好"GTSvrCommData"这个全局共享的缓冲MapFile
->进入LoadFont函数载入需要用的字体,LoadFont函数第二个参数决定是载入chn12.dzf和asc12.dzf图像组成的字体,还是chn12.dot和eng12.dot普通的字体。因为固定的第二个参数为TRUE,所以总是载入dot的字体
->查找\plugins*.dll下面的每一个DLL,并LoadLibrary载入它,以及调用其的RegisterGTPlugIn函数。plugins和gtsp.dll之间用一个结构来交换信息,其中0x14偏移处保存的是plugin的版本函数,0x28处是gtsp.dll的版本函数。会互相验证,这个是0x10的版本。
->plugins们载入完成后,gtsp.dll直接Hook本进程空间(汉化版的Kingdoms.exe)里的CreateWindowExA和ExitProcess函数。其中CreateWindowExA和ExitProcess二个函数Hook过去的执行的wapper中,会判断每一个plugins需要的名称是否和函数参数中的一样,是则会调用gtsp.dll和plugin间结构中的0x20偏移处的函数。->最后是打印gtsp.dll初始化成功的信息。
这就是gtsp.dll中会被执行的功能了,虽然我从之前gtsp.dll被载入到的地址猜测到了它工作的原理和怎么修改的方法,但还是完整的看下plugin中会干些什么再下定义。
在汉化版中,plugins只有一个,就是PlugIns\HookKingdoms.dll了。
有了上面分析gtsp.dll的经验,这次也会从dllmain在开始看,因为它是一个DLL最早被执行的代码。
可惜的是HookKingdoms.dll中的DllMain没有什么让人感兴趣的东西,它的过程为,首先获得到DLL自己的路径,接着设置log文件的路径为c:/GTLog.txt
那让我们检查下TAK中文版比原版TAK多执行的代码里面的最后一段的功能吧。
也就是HookKingdoms.RegisterGTPlugIn
在RegisterGTPlugIn中,首先调用版本函数判断gstp.dll的版本是否为0x10
->接着填充了Plugins结构中的的回调表,包括
PlugiClass.GetPluginVersion
PlugiClass.OnCreate
PlugiClass.CreattWndEx_CallBack
PlugiClass.AllEventCallBack
PlugiClass.EnsureRenderOption
其中OnCreate会在gstp载入完plugins的之后,又回来调用到
CreattWndEx_CallBack则会在CreateWindowExA函数的HOOK被触发时调用到。
AllEventCallBack会在CreateWindowExA和ExitProcess的HOOK触发时都调用到。
EnsureRenderOption还没有发现gstp.dll使用它的地方。但其功能是确保TAK的Render设置为某一个值。
填充完成回调函数后,RegisterGTPlugIn的功能就完成了。
接着在回调回来的OnCreate中,分别调用
PlugiClass.SetProcNotidyName设置了ExitProcess要注意的进程名称为,Kingdoms.icd
PlugiClass.SetWndNotifyName设置了CreateWindowsExA要注意的窗口类名称为,Kingdoms Class
这样,当窗口类名称为Kingdoms Class的窗口被创建时,HookKingdoms.CreattWndEx_CallBack就会被执行到了,那我们跟着执行过程继续看CreattWndEx_CallBack回调函数中,会把和它在同一目录中的kingdoms.txt文件,以调用gstp.dll的pluginclass虚函数列表中提供的MakeMapFile和DecodeFile二个函数,完成映射kingdoms.txt到一片内存空间,以及解码,其实就是简单的异或运算得到真正的kingdoms.txt内容的功能。
在解码完成之后,HookKingdoms对TAK的主程序,偏移56A810的位置进行了一个HOOK。在这儿,因为它使用了硬编码,所以其他的TAK主程序都是不配套的,只有这一个主程序是可以使用汉化文件的。
因为我对TA的主程序分析的实在太多异常熟悉了,打开这个地址的只看了一下代码的样子,就已经知道了他hook的是TAK显示字符串的函数,至于具体的显示过程,又和TAK的程序有关系需要解释,所以就不看它这个HOOK执行过去的函数是怎么处理的了。
AllEventCallBack中其实只处理了ExitProcess的情况,没有管CreateWindowsExA,在ExitProcess进程关闭时,HookKingdoms会在它的回调函数中消除掉自己的HOOK,并调用gstp.dll提供的函数CloseMapFile关闭掉自己刚才映射的kingdoms.txt。
解码kingdoms.txt的过程是这样的,大家可以自己弄出来看看。
ARGC char * Data, int len, BYTE * XorKey
int Data;
while ( len > 0 )
{
if ( *XorKey != *Data )
*Data ^= *XorKey;
++(*XorKey);
–len;
++Data;
}
5
至此,汉字就在TAK里显示出来了。当然,执行到这儿在98下是显示出来了,win7下还没呢。可是分析到这儿,似乎没问题啊,一切流程和代码都看着很有道理。
而问题,则是出在很明显的,win98和之后的系统不一样的地方。
在上面HOOK的时候,特意提到了HOOK的是本进程空间的代码,但这个gstp.dll,还有HookKingdoms.dll却都是在kingdom.exe这个程序的进程空间里进行的HOOK,而我们TAK真正的程序,kingdom.ICD是另外一个进程。这就像,gstp.dll和HookKingdoms.dll忙活了一整天盖好了第7层楼还给装修了,却找错自家工地了。
那为什么win98却可以呢?这是因为win98下面,每个进程都可以访问到高2GB地址的系统内存空间,系统内存空间每个现代系统中都是同一份同时存在于所有进程的虚拟空间里面的,也就是说,win98里相当于所有进程都共享了一片内存。这儿要HOOK的二个函数,又刚好在win98存在于系统的那一片里,所以只要HOOK了随便一个进程里的它们,就可以监视到所有进程中的执行了。
既然这样,我们已经分析完了执行的具体,只需要把gstp.dll的DllMain和InitGameServer在kingdom.ICD中执行下,完成汉化的HOOK就存在于它应该在的位置了!
让我们干吧,DllMain只要一个程序的导入表里存在着它的DLL的函数,就会在那个程序开始执行但没执行到前,先给操作系统调用到我们的DllMain了,所以我们可以给kingdom.ICD的导入表里增加一个InitGameServer和FreeGameServer,并修改kingdom.ICD的WinMain函数,跳转到一个我们可以乱改代码的地方,执行下
InitGameServer ( GetDestopWindow ( ));
FreeGameServer则可以在KingdomHook.dll的ExitProcess的HOOK流程中添进去,当然我没添,因为进程都没了,还free个鬼啊。
但是,这样还是不够的,因为win98的CreateWindowsExA函数和后来版本的这个函数的代码有了微小的区别,我们还需要调整下HOOK的流程,修复二个小bug。
当前面Kingdom.ICD的修改完成后,一执行它,就蹦出来个创造窗口出了错的提示,依照TAK的提示,用动态调试器看下执行到CreateWindowsExA时是怎么个样的,就会发现原来是跳转时的跳多远的长度出了错。这时你可以往CreateWindowsExA被修改的位置,在程序还没执行,函数还没被修改前加一个限制的是写入的断点,这样找到gtsd.dll往这儿要写什么东西的代码位置,再从此处下写入断点,定位到gtsd.dll生成写入到CreatWndEx函数处的那段字符串的代码。到这儿,你就会发现,也可以分析它的bug是什么样的了。
这是一个定位bug的思路,事实上通常都是这样就跟个大头娃娃钻地洞一样来回溯bug的。但是我之前已经把gtsd.dll的代码分析的非常好了,而我又经常写hook用的代码,在分析HOOK的过程中算了几下,就看出来它在InitHookTrunk中有一个bug了,InitHookTrunk里的
sub edx, 5 ; here should be “sub edx,[ebp+ValueLen]”
解释起来挺麻烦,还和gtsd.dll里硬编码的几个位置有关系,所以就让感兴趣的到分析的结果idb里自己看吧
hook的用来执行原函数的代码也有一个bug,调用InitHookTrunk只复制了函数开始6个字节的代码到TrunkFunc中,但保存TrunkFunc的位置的预定义代码的开头却有7个字节,这样当在win98时,前7个字节刚好是系统的是一样的没问题,现在的系统里第7个字节不一样了,会出错。我们还需要把CreatWndEx的TrunkFunc中的前面几个字节清成无意义指令NOP。
至此TAK的汉化也可以在win7上运行了
可是我只测试了xp sp3的某个版本,和我自己用的win 7,具体会不会出错呢,请你记得打小报告。
6
大家都知道TAK就是用TA的代码写的,里面TA的代码也很多。所以我分析了那么多TA的东西,TAK的破解闭着眼睛也搞出来了,搜索下面的3个十六进制串,然后替换成后面的
7E3A5C00
->7E5C5C00
3A5C2573000025633A5C2A2E687069
->5C5C257300002E5C2A2E6870690000
7413FEC380FB5A7ED95E32C05B5DC20400B341EBC78AC3
->EB13FEC380FB5A7ED95E32C05B5DC20400B341EB08B02E