elementclient HOOK 文档.doc
修改 elementclient.exe 文件步骤
一.
1.patcher.dll 中的 hook_p(); 函数的主要功能是修改 elementclient.exe 文件,让其启动时,自动加载我们的插件文件 sougoupy.dll。
2.hook_p 函数首先备份 elementclient.exe 防止我们的程序修改后,游戏不能运行,方便用户修复使用。
3.hook_p 修改 elementclient.exe 文件,把插件 sougoupy.dll 的全路径写到其代码段的零区域,此处文件地址定位:0x797801。
4.执行文件 hook 的地址如下:
(图 1.1)
00B97729 0000 add byte ptr ds:[eax],al
00B9772B 0000 add byte ptr ds:[eax],al
00B9772D 0000 add byte ptr ds:[eax],al
00B9772F 0000 add byte ptr ds:[eax],al
00B97731 0000 add byte ptr ds:[eax],al
00B97733 0000 add byte ptr ds:[eax],al
00B97735 0000 add byte ptr ds:[eax],al
00B97737 0000 add byte ptr ds:[eax],al
00B97739 0000 add byte ptr ds:[eax],al
00B9773B 0000 add byte ptr ds:[eax],al
PS:以上的地址将会写入具体的 hook 代码,然后跳转回 (图 1.1)所指定的地址。
修改后的 hook code 代码为:
00B97729 60 pushad
00B9772A 68 0178B900 push elementc.00B97801 ; ASCII "C:\win\hell\1.dll"
00B9772F FF15 9482B900 call dword ptr ds:[<&KERNEL32.LoadLibraryA>] ; kernel32.LoadLibraryA
00B97735 61 popad
00B97736 8965 E8 mov dword ptr ss:[ebp-18],esp
00B97739 33DB xor ebx,ebx
00B9773B ^ E9 4898F7FF jmp elementc.00B10F88
//二进制为:
60 68 01 78 B9 00 FF 15 94 82 B9 00 61 89 65 E8 33 DB E9 48 98 F7 FF
patcher HOOK 文档.doc
修改 Patcher.exe 文件步骤
一.
1.在文件偏移 $64100 处,写入外部dll 所在的全路径。(ps:此处的外部 dll,功能为拦截住用户点“开始”游戏时,保证 elementclient.exe 文件被正确改正。)
此处外部 dll 名称定义为:patcher.dll
2.我们的启动程序做的 inline hook :
(图 1.1)
上图画红框的地址,为我们需要inline hook 的地址。
00464035 0000 add byte ptr ds:[eax],al //上图 hook 后会跳转到此处做
00464037 0000 add byte ptr ds:[eax],al //加载外部 dll 的工作,然后跳转
00464039 0000 add byte ptr ds:[eax],al // 回到地址:0x443723
0046403B 0000 add byte ptr ds:[eax],al
0046403D 0000 add byte ptr ds:[eax],al
0046403F 0000 add byte ptr ds:[eax],al
(图 1.2 patcher.exe 的IAT 表)
Ps: iat 表用于汇编写入时,调用系统动态库。
3.Hook
A.
0044371E /E9 12090200 jmp patcher.00464035 //已经修改的地址
//二进制代码:E9 12 09 02 00
对应的文件地址为 : 0044371E - 400000 = 4371E
修改长度:5 字节
B.
00464035 > \60 pushad
00464036 . 68 00414600 push patcher1.00464060
0046403B FF15 DC504600 call dword ptr ds:[<&KERNEL32.LoadLibraryA>]
00464041 61 popad
00464042 E8 6047FEFF call patcher1.004487A7
00464047 ^ E9 D7F6FDFF jmp patcher1.00443723
//二进制代码:60 68 00 4146 00 FF 15 DC 50 46 00 61 E8 60 47 FE FF E9 D7 F6 FD FF
对应的文件地址为 : 00464035 - 400000 = 64035
修改长度:23 字节
4.备份 patcher.exe 文件细节。
A.我们的启动程序首先判断是否存在配置文件,不存在则直接拷贝一份命名为:patcher.exe.bak。
B.当存在备份文件时,修改代码之前,对比我们记录在配置文件中的 lastmd5 值(此值为修改后的 patcher.exe 文件的 md5),如果此时的 patcher.exe 的 md5 值跟 lastmd5 不同时,则说明游戏对 patcher.exe 文件有更新,此时我们再拷贝一份新的 patcher.exe 为 patcher.exe.bak。
C.这样有效的保证了当我们软件修改 patcher.exe 后导致无法运行游戏,用户可以通过我们的软件修复 patcher.exe 文件。
二.
Patcher.dll 做的工作如下:
1.在 CreateProcessW 处进行跳转 hook.
CreateProcessW:
7C802336 k> 8BFF mov edi,edi
7C802338 55 push ebp
7C802339 8BEC mov ebp,esp
7C80233B 6A 00 push 0
7C80233D FF75 2C push dword ptr ss:[ebp+2C]
7C802340 FF75 28 push dword ptr ss:[ebp+28]
修改为:
7C802336 k>- E9 151DC683 jmp patcher.00464050
7C80233B 6A 00 push 0
7C80233D FF75 2C push dword ptr ss:[ebp+2C]
7C802340 FF75 28 push dword ptr ss:[ebp+28]
7C802343 FF75 24 push dword ptr ss:[ebp+24]
Hook 代码处添加为:
00464050 60 pushad
00464051 68 F0404600 push patcher.004640F0
00464056 FF15 DC504600 call dword ptr ds:[<&KERNEL32.LoadLibraryA>] ; kernel32.LoadLibraryA
0046405C 68 E8404600 push patcher.004640E8
00464061 50 push eax
00464062 B8 40AE807C mov eax,kernel32.GetProcAddress
00464067 FFD0 call eax
00464069 FFD0 call eax
0046406B 61 popad
0046406C B8 3623807C mov eax,kernel32.CreateProcessW
00464071 83C0 05 add eax,5
00464074 8BFF mov edi,edi
00464076 55 push ebp
00464077 8BEC mov ebp,esp
00464079 FFE0 jmp eax
PS:由于 IAT 表,对于 GetProcAddress,CreateProcessW 两个函数地址的记录不完全,所以需要在 patcher.dll 模块中,计算函数地址,然后再次写入回去即可。
//***************************************************
同时添加字符串("hook_p","patcher.dll"):
004640E8 68 6F6F6B5F push 5F6B6F6F
004640ED 70 00 jo short patcher.004640EF
004640EF 0070 61 add byte ptr ds:[eax+61],dh
004640F2 74 63 je short patcher.00464157
004640F4 68 65722E64 push 642E7265
004640F9 6C ins byte ptr es:[edi],dx
004640FA 6C ins byte ptr es:[edi],dx
PS:此时,当玩家点击游戏的 “开始”按钮时,就会首先执行我们的 hook_p 函数
library patcher;
uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls,Registry,shellapi,MD5,IniFiles;
//-------------------  patcher.exe ---------------
 const PAGE_EXECUTE_READWRITE = $40;
 const hook_array_jmp:array[0..4] of byte = ($E9,$15,$1D,$C6,$83);
 const hook_array_string:array[0..19] of Byte =($68,$6F,$6F,$6B,$5F,$70,$00,
                                               $00,$70,$61,$74,$63,$68,$65,
                                               $72,$2E,$64,$6C,$6C,$00);
 const hook_array_code:array[0..42] of byte = ($60,$68,$F0,$40,$46,$00,$FF,
                                               $15,$DC,$50,$46,$00,$68,$E8,
                                               $40,$46,$00,$50,$B8,$40,$AE,
                                               $80,$7C,$FF,$D0,$FF,$D0,$61,
                                               $B8,$36,$23,$80,$7C,$83,$C0,
                                               $05,$8B,$FF,$55,$8B,$EC,$FF,
                                               $E0);
  //-------------------- elementclient.exe  ---------
 const element_hook_array_jmp:array[0..4] of byte = ($E9,$A1,$67,$08,$00);
 const element_hook_array_code:array[0..22] of byte = ($60,$68,$01,$78,$B9,
                                               $00,$FF,$15,$94,$82,$B9,$00,
                                               $61,$89,$65,$E8,$33,$DB,$E9,
                                               $48,$98,$F7,$FF);
 var
   thisDllIni:TIniFile;
   ini_fileName:string = 'config.ini';                                           //配置文件名称
   //-------------------  patcher.exe ---------------
   iat_patcher_createProcessW_add:DWORD;                                         // iat 表中 CreateProcessW 函数地址
   iat_patcher_virtualProtectEx_add:DWORD;                                       //iat 表中  VirtualProtectEx 函数地址
   getprocaddress_add:dword;
   patcher_dll_name:string = 'patcher.dll';                                      //patcher 中的 dll 名称
   plugin_dll_name:string = 'sogoupy.dll';                                       //插件的名称
   work_dirpath:string;                                                          //工作目录
   current_dirpath:string;                                                       //当前模块所在路径
   elementclient_path:string;                                                    //游戏客户端路径
   elementclient_file_name:string = 'elementclient.exe';                         //游戏客户端名字
   hook_jmp_add:dword;                                                           //hook 代码跳转的地址
   hook_code_add:dword = $464050;                                                //hook 代码的地址
   hook_add_string_addr:DWORD = $4640e8;                                         //添加字符串到更新程序中
   plugin_pathAdd_toWrite_inPatcher:dword = $64100;                              //插件全路径在游戏中的记录
  //-------------------- elementclient.exe  ---------
   element_plugin_full_path_add:dword = $00B97801 - $400000;                     //在游戏文件中,插件的全路径
   element_jmp_file_hook_add:dword = $00B10F83 - $400000;                        //在文件中 hook 的地址
   element_code_file_hook_add:dword = $00B97729 - $400000;                       //在文件中写入 hook code 的地址
 {$R *.res}
 //***********************************************
 procedure getFuncAdd();                                                         //获取函数地址
 var
   h:dword;
 begin
   h:=LoadLibraryA('kernel32.dll');
   iat_patcher_createProcessW_add:=DWORD(GetProcAddress(h,'CreateProcessW'));
   iat_patcher_virtualProtectEx_add:=DWORD(GetProcAddress(h,'VirtualProtectEx'));
   getprocaddress_add:=DWORD(GetProcAddress(h,'GetProcAddress'));
   FreeLibrary(h);
 end;
//************************************************                              //修改内存属性
 function modifyMemory(startAdd,attr,size:dword):Boolean;
 var
   retValue:dword;
   hProcess:dword;
   num:dword;
   c:pointer;
 begin
   hProcess:=GetCurrentProcess();
   result:=false;
   retValue:=0;
   c:=@num;                                                                      //此处一定要用指针,否则函数执行失败
    asm
       pushad
       mov ecx,c
       push ecx
       mov ebx,attr
       push ebx
       mov ebx,size
       push ebx
       mov ebx,startAdd
       push ebx
       mov ebx,hProcess
       push ebx
       mov ebx,iat_patcher_virtualProtectEx_add
       call ebx
       mov retValue,eax
       popad
    end;
    if(retValue <> 0) then result:=true;
 end; 
 //***********************************************
 procedure hook_process_modify();                                                //hook CreateProcess
 begin
  getFuncAdd();
   hook_jmp_add:= iat_patcher_createProcessW_add;
  modifyMemory(hook_jmp_add,PAGE_EXECUTE_READWRITE,5);                          //修改内存属性
   modifyMemory(hook_code_add,PAGE_EXECUTE_READWRITE,42);                        //修改内存属性
   modifyMemory(hook_add_string_addr,PAGE_EXECUTE_READWRITE,20);                 //修改内存属性
  asm
     pushad                                                                      //执行内存写入  jmp
      lea eax,hook_array_jmp
      xor ecx,ecx
      mov ebx,hook_jmp_add
      @back:
      mov dl,Byte ptr[eax]
      mov Byte ptr[ebx],dl
      inc eax
      inc ebx
      inc ecx
      cmp ecx,5
      jnz @back
     popad
   end;
  asm
     pushad                                                                      //执行内存写入  code
      lea eax,hook_array_code
      xor ecx,ecx
      mov ebx,hook_code_add
      @back:
      mov dl,Byte ptr[eax]
      mov Byte ptr[ebx],dl
      inc eax
      inc ebx
      inc ecx
      cmp ecx,43
      jnz @back
     popad
   end;
  asm
     pushad                                                                      //执行内存写入 string
      lea eax,hook_array_string
      xor ecx,ecx
      mov ebx,hook_add_string_addr
      @back:
      mov dl,Byte ptr[eax]
      mov Byte ptr[ebx],dl
      inc eax
      inc ebx
      inc ecx
      cmp ecx,20
      jnz @back
     popad
   end;
  asm                                                                           //再次修改函数地址
     mov eax,hook_code_add
     add eax,19
     mov ebx,getprocaddress_add
     mov [eax],ebx
    mov eax,hook_code_add
     add eax,29
     mov ebx,iat_patcher_createProcessW_add
     mov [eax],ebx
   end;
 end;
 //***********************************************                               //修改 elementclient.exe 文件
 function modify_elementclient_file(gm_path,plug_full_path:string):boolean;
 var
   f:THANDLE;
   ret:Boolean;
   num,zero:dword;
   ret_offset:dword;
   md5_file_exe:string;
   md5_last_modify:string;
   ini_dll_full_path:string;
 begin
   result:=false;
   md5_file_exe:=MD5DigestToStr(MD5File(gm_path));
   md5_last_modify:=thisDllIni.ReadString('elementclient','lastmd5','');
   ini_dll_full_path:= thisDllIni.ReadString('elementclient','dllpath','');
   if(not FileExists(gm_path + '.bak')) then
   begin
     copyfile(PChar(gm_path),PChar(gm_path + '.bak'),false);
   end
   else
   begin
     if(FileExists(work_dirpath + ini_fileName)) then                            //如果配置文件存在
      begin
        if(Trim(md5_last_modify) <> '') then                                     //如果配置文件中 lastmd5 节点不为空
        begin
          if(md5_file_exe <> md5_last_modify) then
           begin
            copyfile(PChar(gm_path),PChar(gm_path + '.bak'),false);
           end;
        end;
      end;
   end;
if(md5_last_modify = '') or (md5_last_modify <> md5_file_exe) or
    (ini_dll_full_path = '') or
    (ini_dll_full_path <> MD5DigestToStr(MD5String((plug_full_path)))) then
 begin
  f:=CreateFileA(
                  PChar(gm_path),
                  GENERIC_ALL,
                  0,
                  nil,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  0);
   if(f = INVALID_HANDLE_VALUE) then                                            //打开文件失败
    begin
       ShowMessage('插件无法加载,请关闭所有正在运行的游戏再重试');
       Exit;
    end;
  ret_offset:=SetFilePointer(f,element_plugin_full_path_add,nil,FILE_BEGIN);
   if(ret_offset <> element_plugin_full_path_add) then                           //在文件中移动指针失败
   begin
     ShowMessage('移动文件指针失败');
     Exit;
   end;
  //将插件的全路径写入目标程序中
   ret:= writefile(f,dword(Pointer(plug_full_path)^),length(plug_full_path),num,nil);
   if(not ret) then Exit;
   zero:=0;
   writefile(f,zero,1,num,nil);
   //执行 hook 文件的写入 ---- element_jmp_file_hook_add
  ret_offset:=SetFilePointer(f,element_jmp_file_hook_add,nil,FILE_BEGIN);
   if(ret_offset <> element_jmp_file_hook_add) then                           //在文件中移动指针失败
   begin
     ShowMessage('移动文件指针失败');
     Exit;
   end;
   ret:= writefile(f,element_hook_array_jmp,5,num,nil);
   if(not ret) then Exit;
   ret_offset:=SetFilePointer(f,element_code_file_hook_add,nil,FILE_BEGIN);
   if(ret_offset <> element_code_file_hook_add) then                           //在文件中移动指针失败
   begin
     ShowMessage('移动文件指针失败');
     Exit;
   end;
   ret:= writefile(f,element_hook_array_code,23,num,nil);
   if(not ret) then Exit;
   
   CloseHandle(f);
   thisDllIni.WriteString('elementclient','lastmd5',MD5DigestToStr(MD5File(gm_path)));
   thisDllIni.WriteString('elementclient','dllpath',MD5DigestToStr(MD5String(plug_full_path)));
  end;
  result:=True;
 end;
 //***********************************************                               //导出函数,主要用于修改 elementclient.exe 程序
 procedure hook_p();
 var
   ret:Boolean;
   dllPath:string;
   plugin_dll_full_path:string;
   position:integer;
   gameClientFullPath:string;
 begin
   dllPath:= PChar(plugin_pathAdd_toWrite_inPatcher + $400000);
   if(dllPath = '') then
   begin
    showmessage('获取工作目录路径失败');
    Exit;
   end;
  dllPath:=ExtractFilePath(dllPath);
   if(dllPath = '') then
   begin
     ShowMessage('获取的工作目录参数错误');
     Exit;
   end;
  work_dirpath:=dllPath;
   plugin_dll_full_path:=dllPath + plugin_dll_name;                              //插件的全路径
   current_dirpath:=ExtractFilePath(Application.ExeName);
   position:=Pos('patcher',current_dirpath);                                     //获得上层目录字符串
  elementclient_path:=Copy(current_dirpath,1,position - 1);
   elementclient_path:= elementclient_path + 'element\';                         //拼接路径
   gameClientFullPath:=elementclient_path + elementclient_file_name;             //游戏客户端全路径
   thisDllIni:=TIniFile.Create(work_dirpath + ini_fileName);
   ret:=modify_elementclient_file(gameClientFullPath,plugin_dll_full_path);
   thisDllIni.Free;
 end;
 //***********************************************
 exports
   hook_p;
 //***********************************************
 begin
   hook_process_modify();
 end.
  
unit StartGame;
interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls,Registry,shellapi,MD5,IniFiles;
 //------------------------------------------------
 procedure modify_file();                                                        //修改 patcher 文件
//------------------------------------------------
 const hook_array_jmp:array[0..4] of byte = ($E9,$12,$09,$02,$00);               //hook 跳转代码
 const hook_array_code:array[0..22] of byte = (                                  //hook 修改的代码
       $60,$68,$00,$41,$46,$00,$FF,$15,$DC,$50,$46,$00,
       $61,$E8,$60,$47,$FE,$FF,$E9,$D7,$F6,$FD,$FF);
 var
   thisSoftIni:TIniFile;
   update_class_Name:string = 'ZPerfectWorldPatcherClass';
   patcher_path:string;                                                          //game patcher.exe 文件的全路径
   gm_path_registry:string = 'Software\PWRD\zhuxian2';                           //在注册表中保存游戏路径的键
   gm_registry_key:string = 'launcher';                                          //键值
   plugin_name:string = 'patcher.dll';                                           //外部动态库插件名称
   hook_add_jmp_file_offset:DWORD = $4371E;                                      //hook修改文件偏移
   hook_add_jmp_code_offset:DWORD = $64035;                                      //hook修改文件偏移
   plugin_pathAdd_toWrite_inPatcher:dword = $64100;                              //pathcer 文件偏移,用于写入插件的工作目录
   this_soft_ini_fileName:string = 'config.ini';                                 //本软件的配置文件名称
 implementation
 uses Unit1;
//**********  从注册表中读取游戏路径   **********
 function getGamePathFromRegistry():string;
 var
   ARegistry : TRegistry;
 begin
   ARegistry := TRegistry.Create;
   result:='';
    with ARegistry do
    begin
      RootKey:=HKEY_CURRENT_USER;
       if OpenKey(gm_path_registry,false) then
       begin
         result:=ReadString(gm_registry_key);
       end;
       CloseKey;
       Destroy;
    end;
 end;
//**************   修改文件 ***********************
 procedure modify_patcher();
 var
   plugin_dir:string;
   str_to_write:string;
   f:THANDLE;
   ret_offset:dword;
   ret:boolean;
   num:dword;
   zero:Byte;
   md5_file_exe,md5_file_exe_bak:string;
   md5_last_modify:string;
   ini_dll_full_path:string;
 begin
    patcher_path:= Trim(getGamePathFromRegistry());
    if(patcher_path = '') then Exit;
    //备份 patcher 文件
    if(not FileExists(PChar(patcher_path+'.bak'))) then
    begin
     copyfile(PChar(patcher_path),PChar(patcher_path + '.bak'),false);
    end
    else
    begin
      if(FileExists(this_soft_ini_fileName)) then                                //如果配置文件存在
      begin
        md5_last_modify:=thisSoftIni.ReadString('patcher','lastmd5','');
        if(Trim(md5_last_modify) <> '') then                                     //如果配置文件中lastmd5节点不为空
        begin
          md5_file_exe:=MD5DigestToStr(MD5File(patcher_path));
          if(md5_file_exe <> md5_last_modify) then
           begin
            copyfile(PChar(patcher_path),PChar(patcher_path + '.bak'),false);
           end;
        end;
        
      end;
    end;
    plugin_dir:= ExtractFilePath(Application.ExeName);
    str_to_write:= plugin_dir + plugin_name;                                     //需要加载 dll 的全路径
ini_dll_full_path:=thisSoftIni.ReadString('patcher','dllpath','');
   if(ini_dll_full_path = '') or (ini_dll_full_path <> MD5DigestToStr(MD5File(str_to_write))) then
    begin
    f:=CreateFileA(
                  PChar(patcher_path),
                  GENERIC_ALL,
                  0,
                  nil,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  0);
if(f = INVALID_HANDLE_VALUE) then Exit; //打开文件失败
   ret_offset:=SetFilePointer(f,plugin_pathAdd_toWrite_inPatcher,nil,FILE_BEGIN);
    if(ret_offset <> plugin_pathAdd_toWrite_inPatcher) then Exit;                //在文件中移动指针失败
   ret:= writefile(f,dword(Pointer(str_to_write)^),length(str_to_write),num,nil);
    if(not ret) then Exit;                                                       //写入外部dll路径失败
    zero:=0;
    writefile(f,zero,1,num,nil);
    thisSoftIni.WriteString('patcher','dllpath',MD5DigestToStr(MD5File(str_to_write)));                    //写入配置文件
    CloseHandle(f);
    end;
 end;
//*****  修改代码,让其主动加载外部 dll  **********
 function modify_code_for_load_dll():boolean;
 var
   f:THANDLE;
   ret:boolean;
   last_md5_str:string;
   ret_offset,num:dword;
 begin
    result:=false;
    last_md5_str:=thisSoftIni.ReadString('patcher','lastmd5','');
    if (last_md5_str = '') or (MD5DigestToStr(MD5File(patcher_path)) <> last_md5_str) then
    begin
    f:=CreateFileA(
                  PChar(patcher_path),
                  GENERIC_ALL,
                  0,
                  nil,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  0);
                  
   if(f = INVALID_HANDLE_VALUE) then Exit;                                      //打开文件失败
   ret_offset:=SetFilePointer(f,hook_add_jmp_file_offset,nil,FILE_BEGIN);
   if(ret_offset <> hook_add_jmp_file_offset) then Exit;                        //在文件中移动指针失败
   ret:= writefile(f,hook_array_jmp,5,num,0);
   
    ret_offset:=SetFilePointer(f,hook_add_jmp_code_offset,nil,FILE_BEGIN);
   if(ret_offset <> hook_add_jmp_code_offset) then Exit;                        //在文件中移动指针失败
   ret:= writefile(f,hook_array_code,23,num,0);
   CloseHandle(f);
   thisSoftIni.WriteString('patcher','lastmd5',MD5DigestToStr(MD5File(patcher_path)));
   end;
   Result:=True;
 end;
 //*************************************************                             //修改文件入口函数
 procedure modify_file();
 var
   h:hwnd;
 begin
   h:=FindWindow(PChar(update_class_Name),nil);
   if(h <> 0) then
   begin
     ShowMessage('你已经运行了一个更新程序');
     Exit;
   end;
  thisSoftIni:= TIniFile.Create(ExtractFilePath(Application.ExeName)+this_soft_ini_fileName);
  modify_patcher();
  modify_code_for_load_dll();
  thisSoftIni.Free;
 end;
//**************************************************
 end.