原文转自:http://lujuns.spaces.live.com/blog/cns!29d3b1627d1efea1!232.entry
近日在做一软件汉化工作时,由于原软件的标题在程序内部会根据运行状态发生变化,且其中的Tab Contrl控件的标签也是由代码写死的,无法使用资源编辑器进行修改,而且当时没有找到合适的工具来对PE文件进行修改,而且这种方法由于没有做过没有经验,难以保证原有系统的稳定性,所以就采用了在原有程序外面挂接一个程序,来修改原有软件的这部分文字。
对于程序的标题栏,在外接程序中使用FindWindow及SetWindowText等函数很快就解决问题,但是在修改Tab Control的标签时,却遇上了极大的麻烦,问题描述如下:Tab Control控件本身提供了数种操纵消息,而针对这些消息,微软又提供了一套宏,以方便操作,例如使用TabCtrl_GetItemCount宏来获得Tab Control上的页数,使用TabCtrl_GetItem来获得其中某一页的内容,使用TabCtrl_SetItem来设置某一页的内容。鉴于以上信息,很自然的想到了在外接程序种使用TabCtrl_SetItem来修改Tab Control上某一页的标题,而且很快的,我也在其中写出了代码。然而,问题出现了,该宏并不工作。
后来,几经查找,终于找到了原因:外接程序和目标程序,在运行时分别属于两个进程,而TabCtrl_SetItem宏使用SendMessage发送消息,其中一个比较重要的参数是一个TCITEM结构的指针,而此结构种pszText又是一个TCHAR的指针,存储着要设置的字符串资源,然而该TCITEM结构的指针,只在外接程序中有效,到了目标进程中就成了一个野指针。
归结起来,这个问题又落到了进程间通信,然而,由于目标程序是已经固定了的,没有源代码,难以修改,所以即使解决了进程间的通信问题,那么问题也仍然难以解决。于是想到了,在目标进程中,注入一段代码,来完成这个工作。首先想到的是DLL注入,但是由于这个问题似乎比较简单,懒得重新开个项目,做个DLL出来,也就是说使用一个比较简单的方法往目标进程中注入一段代码,完成任务即可,于是想到了使用CreateRemoteThread来往目标进程中注入一个线程,在此线程中修改界面的标题,并调用TabCtrl_SetItem等函数来完成任务。
于是,很快又参考MSDN中关于CreateRemoteThread函数的规范,写出了线程函数和创建该线程的代码,在VC6中,F5,期望着出现结果,然而,再次,令人失望的是,线程并没有跑起来,使用任务管理器查看,发现目标进程中的线程数并没有任何变化,也就是说,线程创建失败,然而,在外接程序代码中,单步跟踪后,发现线程成功创建,那么,问题出在哪里呢?
经过一再的详细阅读MSDN中的说明,忽然发现,原来是远程线程的启动函数的地址出了问题,该函数要求,线程函数的地址要是在目标进程中的地址,而我直接传递的是本地进程中的地址,这样在目标进程中肯定找不到可执行的代码,于是使用VirtuAllocEx函数在目标进程分配控件,使用WriteProcessMemory函数往目标进程拷贝资源,几经周折后,终于让线程跑了起来,并完成了任务。
这里是使用CreateRemoteThread函数时的步骤:
① 使用VirtualAllocEx函数在目标进程中分配空间,用以容纳线程函数代码。
② 使用WriteProcessMemory函数把本地进程中线程函数的代码拷贝到目标进程中。
③ 在目标进程中分配线程参数的空间。
④ 把本地进程中设置好的线程参数拷贝到目标仅此中。这里要注意的是,线程参数中最好不要有指针。
⑤ 使用CreateRemoteThread函数创建远程线程。
⑥ 等待远程线程结束。
⑦ 当远程线程结束后,使用VirtualFreeEx函数释放前面所分配的空间。并使用CloseHandle关闭线程句柄。
还有几点值得注意的是:
① 在远程进程中使用到的函数,例如SetWindowText、SendMessage等,都需要是目标进程空间中地址。也就是说,要使用LoadLibrary、GetProcAddress函数来获得函数的地址。——这里要说明的一点是,LoadLibrary、GetProcAddress函数的地址,来自与Kernel32.dll库,而这个库,在Windows平台中,是被映射到相同的地址空间的,所以在本地进程中找到的地址可以在目标进程中使用。
② 线程参数中如果涉及到指针,则自己要保证这些指针在目标进程中有效。
③ 给代码空间和参数空间分配的空间大小,一定要足够容纳代码和参数。