以前谈到过手动PEDIY魔兽Game.dll让warkey与其生死相依。现在的魔兽已经更新到1.24版了,但基本上我们还在用1.20e版,为啥?对战平台上还是支持1.20啊!这样就造成一个问题:看魔兽rep的时候得更改版本,看完以后呢,又得改回来!
 
  以前手动PEDIY的DLL得单独存放,更新以后又得再复制进去,于是本人又觉得麻烦了,能不能变手动为自动呢?答案是肯定的!然而在这期间本人也遇到了一些问题,特此留档,欢迎指教!
 
  我想的是比较简洁地利用节间隙的办法来插入代码,这就要求代码尽可能的短而有效。前面说过的PEDIY中的三个函数FindWindowA, SendMessageA, ShellExecuteA(此函数由WinExec代替)在代码中还是会用到,如果用SHELLCODE的办法直接插入代码到.text节间隙,很可能会空间不够用!因为必须首先要查找到三个函数的地址才能调用它,所以我还是想添加导入函数的办法来做,这样可以大大简化插入的代码空间。
 
  我参看了LordPE的办法,它是向PE中添加一个新节,然后将要添加的输入表等信息添加到复制来的输入表尾,并连同ThunkData、IAT的信息一起放在所添加的新节中。新节的属性是可读写执行。
 
  但是这样会造成Game.dll的大小再次增大,本来已经够大的,8M左右,不能让大再大下去了,所以我放弃添加新节的方法,将导入函数信息等都放在原输入表所在的节: .rdata
此节属性为只读。当我成功添加好函数的时候,发生了一个郁闷了我整整一天的问题:Game.dll不能正常启动!我想到可能是rdata节属性为只读,于是我将其属性加上了“可写”。于是Game.dll被正常载入,启动也正常了。
 
  大家想到这个问题了吗:同样是在.rdata节,为什么其它的IAT地址表能被正常填充,而我在此节添加的导入函数IAT却不能被写入?我百思不得其解!幸好得到了figo的帮助,虽然他没有直接回答我的问题,但却提醒了我:肯定是IAT表所在的位置有问题,但同样是在rdata区段,为什么就不能填充呢?难道非得改此节的属性吗,但我又不想轻意这样做。而且还想找到这个问题的答案。
 
  我思考了一个晚上,第二天在查看此dll的数据目录的时候眼前一亮,答案原来就在眼前!如下图所示,数据目录在存在一个IAT表,这里指定了IAT所在的地址与大小。分析一下其数据就能发现,这个IAT所在的位置正好在rdata的前端部分!而在这一部分的IAT表在加载的时候能够被正常填充,这说明什么?说明ExeLoader在载入此节的时候会对数据目录中的IAT表所指定的区域属性进行更改!至少会加上一个“可写”的属性。所以,我们只要将此IAT表的大小扩展到覆盖住添加的IAT所在的位置就能起到让此DLL文件正常被载入的目的。
 

 
  解决了这个问题以后,后面的事情就好办多了。由于是dll,所以可能涉及到重定位的问题,添加到rdata节段的由FirstThunk值+实际的基址值才能得到真实的函数地址。形如: call dword ptr[FirstThunk + ImageBase]。我尝试用PEB的ImageBase,但是不对!这个值是载入者进程的基址,而我想要的是此DLL的基址,这是我遇到的第二个问题:如何得到当前DLL的ImageBase值呢?
 
  呵呵,这个问题其实也很简单,用SDK开发过DLL的人都知道DllMain函数:
  BOOL APIENTRY DllMain( HANDLE hModule, 
    DWORD ul_reason_for_call, 
    LPVOID lpReserved
  )
  {
    return TRUE;
  }
 
  看到没有,第一个参数就是hModule,也就是我们朝思暮想的ImageBase,这个参数在加载的时候就会被ExeLoader压入堆栈,所以想得到它也是轻而易举: 
  mov eax, [esp+4] ; 这样eax值就是当前DLL的基址
 
  最后,为了防止多次添加,得对文件作一个标记。我选择在Dos头的e_lfanew的前一个字节作上标记:0xff,每次运行的时候检查一下,就能确定文件是否已经添加附加启动信息了。最后可以看到自动附加的代码:
 

 
  是不是比手动PEDIY的更加简洁呢?给出原码,欢迎大家指点!

上传的附件 魔兽附加启动器(改进版).rar