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.