看本文前必须先会ASM,VC,OD,FPE,HOOK
1 游戏修改
我一直用的是FPE2000,不习惯用其他的修改器,因为有个功能其他的修改器没有,而且这个功能相当重
我一直用的是FPE2000,不习惯用其他的修改器,因为有个功能其他的修改器没有,而且这个功能相当重
要。
怎样搜索就不讲了,主要讲分析。先看血和魔法,大家会发现一般的游戏血和魔法都在相临的位置,为什
怎样搜索就不讲了,主要讲分析。先看血和魔法,大家会发现一般的游戏血和魔法都在相临的位置,为什
么哪?这个原理很简单,因为编程人员的习惯问题。写游戏时会定义一个基本的结构,这个结构包含人物
的一些属性,例如:
struct _CHAR_ATTR
{
char Name[30]
DWORD HP,MAXHP;
DWORD MP,MAXMP;
DWORD Exp;
}
这样一来程序运行后分配内存是按结构分配的,所以直接分配一个结构的大小并不考虑结构中的变量,那
struct _CHAR_ATTR
{
char Name[30]
DWORD HP,MAXHP;
DWORD MP,MAXMP;
DWORD Exp;
}
这样一来程序运行后分配内存是按结构分配的,所以直接分配一个结构的大小并不考虑结构中的变量,那
么结构中的变量地址当然是相临的。所以一般查到HP的时候就可以查到1个人物的一些基本属性了。以经
验来讲,人物的动作也在这个结构附近,这里就要介绍只有FPE有的重要功能了。动作一般是一些数字表
示,当人物做一个砍怪的动作时,会分N个细节的动作,一般这些动作都是连续的数字,好比0是站立准备
砍怪,1是举起武器,2是往下挥武器,3是砍到怪,4是收起武器,然后就是循环这几个动作,那我们怎样
找这个地址哪,首先找到HP地址,然后在FPE上点EDIT页面,这时FPE显示的数据是点击edit时的数据,再
转回游戏,不要动也不要掉血,不然就不准了,再转回FPE,按下F5(这个就是我说的重要功能),有没
有看到一些地址的数据已经变了,没错这些数据变动的数据就是人物的骨骼动画数据,如果没有发现变动
也不要紧,按PAGE UP或者PAGE DOWN,看看附近的内存页是否有数据在变动,接下来就是分析这些动画数
据了,我们再回游戏,然后让你的人物跑起来,要跑的比较远不然还没等转到FPE就停了,跑起来后我们
转回FPE,然后一直按着F5,看数据的变化,主要是寻找*循环*变动的数据,可能刚开始找这些不会太明
白,不过没关系,凭知觉,看哪个象就在哪个地址锁定,锁定完后再去游戏跑跑看,如果人物跑的动作不
对的话那就证明你成功了,接下来用同样的方法找打怪的动作,找到后就可以做光人物加速而游戏速度不
变的功能了:) 什么?到现在你不还不知道怎么做人物加速?那我再费点手力,还看上面0、1、2、3、4
动作,我们可以直接去掉0、1、2两个动作,在FPE里锁定值是3,判断是=0,就是当做0动作时直接跳到3
动作。这样就直接是砍怪和收武器的动作了作了,如果还嫌慢那就把4也去掉,FPE里同一地址锁定值是0
,判断是3,这样光剩下砍的动作了,做完后回游戏看看,发现你的任务砍怪时一直在抽筋:)
FPE里还有个重要的搜索就是'?',用这个可以搜索条状数值,如果不会的话可以哪一些单机游戏做实验。
FPE里还有个重要的搜索就是'?',用这个可以搜索条状数值,如果不会的话可以哪一些单机游戏做实验。
2 窗口化
2D游戏窗口化
主要是修改窗口类型。
//窗口类型和窗口扩展类型
LONG style,exstyle
//得到窗口类型
style= GetWindowLong(窗口句柄,GWL_STYLE);
//加上标题栏
style=style | WS_CAPTION ;
//这里就是把游戏设置成窗口模式了
SetWindowLong(窗口句柄,GWL_STYLE,style);//修改窗体的exstyle属性
//对于扩展类型可以改也可以不改,看自己喜好了。
exstyle=GetWindowLong(窗口句柄,GWL_EXSTYLE);
exstyle=exstyle | WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
//设置窗口的扩展类型
SetWindowLong(窗口句柄,GWL_EXSTYLE,exstyle);
//这里是设置窗口的大小,并且设置置顶的属性
SetWindowPos(窗口句柄,HWND_NOTOPMOST,0,0,800,600,SWP_SHOWWINDOW);
//显示窗口
ShowWindow(窗口句柄,SW_SHOWNORMAL);
//接下来是修改窗口的消息了,一般游戏会在改变窗口模式消息检测是否是全屏模式
//这个变量用来保存游戏窗口原先的消息处理函数
WNDPROC OldMsgProc;
//得到原来的消息函数
OldMsgProc=(WNDPROC)GetWindowLong(窗口句柄,GWL_WNDPROC);
//接下来我们就替换自己的窗口消息函数
if(SetWindowLong(窗口句柄,GWL_WNDPROC,(long)MsgProc)==0)
return false;
//窗口消息函数
LRESULT CALLBACK MsgProc(HWND hWnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
//消息过滤
switch (msg)
{
case WM_ACTIVATEAPP:
case WM_ACTIVATE:
case WM_KILLFOCUS:
case WM_SETFOCUS:
case WM_CLOSE:
return 0;
//杀掉检测窗口模式的定时器
case WM_TIMER:
if(wparam==检测窗口模式的定时器ID)
KillTimer(hWnd,wparam); //杀掉
break;
}
return CallWindowProc(OldProcMsg,hWnd,msg,wparam,lparam);
}
这样就完成窗口化了,还有一点需要注意的,就是如果游戏启用定时器检测窗口状态时我们必须把这些定
2D游戏窗口化
主要是修改窗口类型。
//窗口类型和窗口扩展类型
LONG style,exstyle
//得到窗口类型
style= GetWindowLong(窗口句柄,GWL_STYLE);
//加上标题栏
style=style | WS_CAPTION ;
//这里就是把游戏设置成窗口模式了
SetWindowLong(窗口句柄,GWL_STYLE,style);//修改窗体的exstyle属性
//对于扩展类型可以改也可以不改,看自己喜好了。
exstyle=GetWindowLong(窗口句柄,GWL_EXSTYLE);
exstyle=exstyle | WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
//设置窗口的扩展类型
SetWindowLong(窗口句柄,GWL_EXSTYLE,exstyle);
//这里是设置窗口的大小,并且设置置顶的属性
SetWindowPos(窗口句柄,HWND_NOTOPMOST,0,0,800,600,SWP_SHOWWINDOW);
//显示窗口
ShowWindow(窗口句柄,SW_SHOWNORMAL);
//接下来是修改窗口的消息了,一般游戏会在改变窗口模式消息检测是否是全屏模式
//这个变量用来保存游戏窗口原先的消息处理函数
WNDPROC OldMsgProc;
//得到原来的消息函数
OldMsgProc=(WNDPROC)GetWindowLong(窗口句柄,GWL_WNDPROC);
//接下来我们就替换自己的窗口消息函数
if(SetWindowLong(窗口句柄,GWL_WNDPROC,(long)MsgProc)==0)
return false;
//窗口消息函数
LRESULT CALLBACK MsgProc(HWND hWnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
//消息过滤
switch (msg)
{
case WM_ACTIVATEAPP:
case WM_ACTIVATE:
case WM_KILLFOCUS:
case WM_SETFOCUS:
case WM_CLOSE:
return 0;
//杀掉检测窗口模式的定时器
case WM_TIMER:
if(wparam==检测窗口模式的定时器ID)
KillTimer(hWnd,wparam); //杀掉
break;
}
return CallWindowProc(OldProcMsg,hWnd,msg,wparam,lparam);
}
这样就完成窗口化了,还有一点需要注意的,就是如果游戏启用定时器检测窗口状态时我们必须把这些定
时器关掉,可以用spy++检测游戏窗口用了哪几个定时器。然后记录下来定时器的ID,在窗口消息
3D游戏窗口化
3D游戏就比较简单了,主要是靠的是DirectX中的CreateDevice函数,当然每个dx版本创建都不一样,但
3D游戏就比较简单了,主要是靠的是DirectX中的CreateDevice函数,当然每个dx版本创建都不一样,但
基本步骤都差不多。只要修改D3DPRESENT_PARAMETERS结构就可以实现了。
我们只要hook dx的CreateDevice,在其中把D3DPRESENT_PARAMETERS.Windowed属性改为true即可。不过
我们只要hook dx的CreateDevice,在其中把D3DPRESENT_PARAMETERS.Windowed属性改为true即可。不过
可能导致游戏不能正常运行或者画面位移、透明等,这些就要参考dx的D3DPRESENT_PARAMETERS结构另外
的参数了。可以参考任意一个D3D教程,里面都有详细的解释。
3 游戏加速
游戏加速是利用修改时间函数的返回值。
利用hook修改返回值应该是很简单的,我就不讲了,但是有些游戏当你利用hook修改后他却提示你修改函
游戏加速是利用修改时间函数的返回值。
利用hook修改返回值应该是很简单的,我就不讲了,但是有些游戏当你利用hook修改后他却提示你修改函
数被修改而终止游戏。不过不要怕只要会汇编没什么能难倒的。
下面的函数就是修改了GetTickCount()函数的返回值用vc写的。下面是在hook初始化时做的工作。
DWORD dwIdOld1=0;
DWORD *p1=((DWORD*)GetTickCount)+3;
VirtualProtectEx(GetCurrentProcess(),(LPVOID)p1,2,PAGE_READWRITE,&dwIdOld1);
if(m_Speed==1)
{
__asm
{
push eax
mov eax,p1
mov [eax+1],0x17 //加1倍速度
pop eax
}
}
else
{
__asm
{
push eax
mov eax,p1
mov [eax+1],0x16 //加2倍速度
pop eax
}
}
VirtualProtectEx(GetCurrentProcess(),(LPVOID)p1,2,dwIdOld1,&dwIdOld1);
这个我就不多讲了因为想要源代码的可以找我。
下面的函数就是修改了GetTickCount()函数的返回值用vc写的。下面是在hook初始化时做的工作。
DWORD dwIdOld1=0;
DWORD *p1=((DWORD*)GetTickCount)+3;
VirtualProtectEx(GetCurrentProcess(),(LPVOID)p1,2,PAGE_READWRITE,&dwIdOld1);
if(m_Speed==1)
{
__asm
{
push eax
mov eax,p1
mov [eax+1],0x17 //加1倍速度
pop eax
}
}
else
{
__asm
{
push eax
mov eax,p1
mov [eax+1],0x16 //加2倍速度
pop eax
}
}
VirtualProtectEx(GetCurrentProcess(),(LPVOID)p1,2,dwIdOld1,&dwIdOld1);
这个我就不多讲了因为想要源代码的可以找我。
4 写屏
大多数人是利用修改游戏函数写屏的,我的方法是HOOK
大多数人是利用修改游戏函数写屏的,我的方法是HOOK
dx写屏。原理很简单,游戏是要通过Blt和BltFast转换页面的,我将字写到后台页面就可以了,好处是不
必太麻烦找游戏输出函数,而且换个游戏也一样能用。而且还能贴个图片到游戏。坏处是如果dx版本不同
就要修改代码了。建议用MS的detours,方便而且稳定。
//输出文字到一个页面
HRESULT DrawText(LPDIRECTDRAWSURFACE m_pdds,TCHAR* strText,DWORD dwOriginX,DWORD dwOriginY,
COLORREF crBackground,COLORREF crForeground)
{
HDC hDC = NULL;
HRESULT hr;
HFONT hFont=NULL;
if( m_pdds == NULL || strText == NULL )
return E_INVALIDARG;
//输出文字到一个页面
HRESULT DrawText(LPDIRECTDRAWSURFACE m_pdds,TCHAR* strText,DWORD dwOriginX,DWORD dwOriginY,
COLORREF crBackground,COLORREF crForeground)
{
HDC hDC = NULL;
HRESULT hr;
HFONT hFont=NULL;
if( m_pdds == NULL || strText == NULL )
return E_INVALIDARG;
// Make sure this surface is restored.
if( FAILED( hr = m_pdds->Restore() ) )
return hr;
if( FAILED( hr = m_pdds->Restore() ) )
return hr;
if( FAILED( hr = m_pdds->GetDC( &hDC ) ) )
return hr;
return hr;
// Set the background and foreground color
SetBkColor( hDC, crBackground );
SetTextColor( hDC, crForeground );
SetBkColor( hDC, crBackground );
SetTextColor( hDC, crForeground );
if( hFont )
SelectObject( hDC, hFont );
SelectObject( hDC, hFont );
// Use GDI to draw the text on the surface
TextOut( hDC, dwOriginX, dwOriginY, strText, strlen(strText) );
TextOut( hDC, dwOriginX, dwOriginY, strText, strlen(strText) );
if( FAILED( hr = m_pdds->ReleaseDC( hDC ) ) )
return hr;
return hr;
return S_OK;
}
//显示文本太简单了,就在HOOK的函数里写1句。
//老版本的BltFast 这个是从离屏页面Copy图片到后台页面的函数
//DefHookDApi 是我自己写的快捷定义hook函数不用去管。detours
DefHookDApi(BltFast,HRESULT,(DWORD x,DWORD y,LPDIRECTDRAWSURFACE lpdds, LPRECT lprc,DWORD
}
//显示文本太简单了,就在HOOK的函数里写1句。
//老版本的BltFast 这个是从离屏页面Copy图片到后台页面的函数
//DefHookDApi 是我自己写的快捷定义hook函数不用去管。detours
DefHookDApi(BltFast,HRESULT,(DWORD x,DWORD y,LPDIRECTDRAWSURFACE lpdds, LPRECT lprc,DWORD
n))
{
//我们直接把东西Copy到离屏页面
DrawText(lpdds,"BltFast",0,0,RGB(0,0,0),RGB(255,255,0));
HRESULT ret=Real_BltFast(x,y,lpdds,lprc,n);
return ret;
}
//老版本的Blt 这个是从后台页面Copy主页面的函数
DefHookDApi(Blt,HRESULT,(GUID FAR *lpGUID,LPRECT lprc,LPDIRECTDRAWSURFACE lpdds,LPRECT
{
//我们直接把东西Copy到离屏页面
DrawText(lpdds,"BltFast",0,0,RGB(0,0,0),RGB(255,255,0));
HRESULT ret=Real_BltFast(x,y,lpdds,lprc,n);
return ret;
}
//老版本的Blt 这个是从后台页面Copy主页面的函数
DefHookDApi(Blt,HRESULT,(GUID FAR *lpGUID,LPRECT lprc,LPDIRECTDRAWSURFACE lpdds,LPRECT
lprc1,
DWORD n, LPDDBLTFX n1))
{
//我们直接把东西Copy到后台页面
DrawText(lpdds,"Blt",0,0,RGB(0,0,0),RGB(255,255,0));
HRESULT ret=Real_Blt(lpGUID,lprc,lpdds,lprc1,n,n1);
return ret;
}
DWORD n, LPDDBLTFX n1))
{
//我们直接把东西Copy到后台页面
DrawText(lpdds,"Blt",0,0,RGB(0,0,0),RGB(255,255,0));
HRESULT ret=Real_Blt(lpGUID,lprc,lpdds,lprc1,n,n1);
return ret;
}
5 分析封包
我是用OD直接解密的,不提倡用wpe看,看的累,而且看半天看不出来东西。
首先是确保游戏运行文件没有加壳。再看看启动后的游戏进程名是否跟你运行的exe名一样,如果不一样那么我们先来看他是如何启动的游戏进程,用od取你运行的exe文件,然后在命令里面输入bp CreateWindowW和bp CreateWindowExW,然后按F9运行,点你连接的服务器,这时会中断下来,然后看他启动的文件和参数,先在启动文件创建个快捷方式,然后把参数填到快捷方式里,以后直接运行这个快捷方式即可。
用od取实际的游戏执行文件,然后在命令行输入bp send,bp recv,bp WSASend, bp WSARecv四个命令,再点od的调试-》参数,把刚才记录的参数写进去。按CTRL+F2重新取。按F9运行。随便输入个帐号和密码点进入,这时会中断到send或者WSASend,按CTRL+F9即可回到游戏领域,然后像上看,加密可能就在这上面。首先我们来判断这个函数的开始,如何判断函数入口那,就是看PUSH语句,od一般把一个函数用蓝线扩起来了,比较容易分清。看函数入口到调用send或WSASend前面有没有call语句,如果有我们需要跟进去,如果发现离函数入口很近而且没有call语句那直接按CTRL+F9再回上一个领域,照这样的方法就可以找到加密算法了。其实很简单。
接下来是recv和WSARecv,首先讲recv,中断这里后我们在堆栈窗口右键点buffer,然后选内存中显示。再按CTRL+F9,发现内存中有了接收的数据,然后在内存的第一个字节右键点读硬件中断,继续按F9运行吧,很快会中断到读取内存的地方,很简单这里有可能就是加密的地方,如果看不出来那么在内存第5个字节同样做读硬件内存中断,很快就能找到解密地方。
WSARecv的buffer不一样,他是个缓存指针,如果中断到这里我们一样在堆栈窗口右键点buffer,然后选内存中显示。这是个结构,前4个是buffer大小,后面的才是数据,按CTRL+F9,发现接收数据,我们直接在第5个地方下读硬件中断,以后就跟recv一样了。呵呵一切都不难,只要会汇编就ok。
我是用OD直接解密的,不提倡用wpe看,看的累,而且看半天看不出来东西。
首先是确保游戏运行文件没有加壳。再看看启动后的游戏进程名是否跟你运行的exe名一样,如果不一样那么我们先来看他是如何启动的游戏进程,用od取你运行的exe文件,然后在命令里面输入bp CreateWindowW和bp CreateWindowExW,然后按F9运行,点你连接的服务器,这时会中断下来,然后看他启动的文件和参数,先在启动文件创建个快捷方式,然后把参数填到快捷方式里,以后直接运行这个快捷方式即可。
用od取实际的游戏执行文件,然后在命令行输入bp send,bp recv,bp WSASend, bp WSARecv四个命令,再点od的调试-》参数,把刚才记录的参数写进去。按CTRL+F2重新取。按F9运行。随便输入个帐号和密码点进入,这时会中断到send或者WSASend,按CTRL+F9即可回到游戏领域,然后像上看,加密可能就在这上面。首先我们来判断这个函数的开始,如何判断函数入口那,就是看PUSH语句,od一般把一个函数用蓝线扩起来了,比较容易分清。看函数入口到调用send或WSASend前面有没有call语句,如果有我们需要跟进去,如果发现离函数入口很近而且没有call语句那直接按CTRL+F9再回上一个领域,照这样的方法就可以找到加密算法了。其实很简单。
接下来是recv和WSARecv,首先讲recv,中断这里后我们在堆栈窗口右键点buffer,然后选内存中显示。再按CTRL+F9,发现内存中有了接收的数据,然后在内存的第一个字节右键点读硬件中断,继续按F9运行吧,很快会中断到读取内存的地方,很简单这里有可能就是加密的地方,如果看不出来那么在内存第5个字节同样做读硬件内存中断,很快就能找到解密地方。
WSARecv的buffer不一样,他是个缓存指针,如果中断到这里我们一样在堆栈窗口右键点buffer,然后选内存中显示。这是个结构,前4个是buffer大小,后面的才是数据,按CTRL+F9,发现接收数据,我们直接在第5个地方下读硬件中断,以后就跟recv一样了。呵呵一切都不难,只要会汇编就ok。
6 找写屏函数的方法
我用si来分析的,因为od我不知道怎么搜索内存。。。
在游戏的交谈栏里写一句话,先不要发送,然后用si的s命令搜索你要发的这句话比如话是‘我要搜索的内存’,s ds:00000000 l ffffffff '我要搜索的内存',有可能会找到多个地址,这个跟fpe的搜索差不多,一个一个修改试试,用“d 地址”命令可以查看找到的地址,然后光标移动到内存页面上面就可以修改了。找到正确的后就要下内存断点了,"bpm 地址 r"命令就可以了,返回游戏把话发出去,这时就会中断到读这段内存的语句了,一般是lea 汇编指令,意思是把这个内存地址副给积存器,然后PUSH,然后就是call了,至于如何分析call有几个参数可以看win32asm教程。有时候也可能复杂点,就是游戏把这个内存拷贝到另一个内存然后再输出,这样就是多了一个步骤而已,只要在拷贝到另一个内存的地址下个内存中断即可。找到函数如何利用哪?在你的hook dll中可以直接调用这个函数,例如这个写屏函数有2个参数,1个是buf,第2个是buf的长度,汇编语句是
mov ebx, ds[????]
push ebx
lea eax,ds[????]
push eax
call ??????
我用si来分析的,因为od我不知道怎么搜索内存。。。
在游戏的交谈栏里写一句话,先不要发送,然后用si的s命令搜索你要发的这句话比如话是‘我要搜索的内存’,s ds:00000000 l ffffffff '我要搜索的内存',有可能会找到多个地址,这个跟fpe的搜索差不多,一个一个修改试试,用“d 地址”命令可以查看找到的地址,然后光标移动到内存页面上面就可以修改了。找到正确的后就要下内存断点了,"bpm 地址 r"命令就可以了,返回游戏把话发出去,这时就会中断到读这段内存的语句了,一般是lea 汇编指令,意思是把这个内存地址副给积存器,然后PUSH,然后就是call了,至于如何分析call有几个参数可以看win32asm教程。有时候也可能复杂点,就是游戏把这个内存拷贝到另一个内存然后再输出,这样就是多了一个步骤而已,只要在拷贝到另一个内存的地址下个内存中断即可。找到函数如何利用哪?在你的hook dll中可以直接调用这个函数,例如这个写屏函数有2个参数,1个是buf,第2个是buf的长度,汇编语句是
mov ebx, ds[????]
push ebx
lea eax,ds[????]
push eax
call ??????
//用vc写个函数,不用怀疑就是那么简单
__declspec(naked) Out(char* buf,int len)
{
mov ebx, len
push ebx
lea eax,buf
push eax
call ??????
}
一般我都做利用recv或WSARecv函数返回失败后做这些功能。
DefHookApi(recv,int,(SOCKET s,char *buf,int len,int flags))
{
int ret=Real_recv(s,buf,len,flags);
if(ret<1&&strcmp(outbuf,""))
{
Out(outbuf,strlen(outbuf));
ZeroMemory( outbuf, sizeof(outbuf) ); //记得清空不然会一直发送
}
}
这样就ok了。
__declspec(naked) Out(char* buf,int len)
{
mov ebx, len
push ebx
lea eax,buf
push eax
call ??????
}
一般我都做利用recv或WSARecv函数返回失败后做这些功能。
DefHookApi(recv,int,(SOCKET s,char *buf,int len,int flags))
{
int ret=Real_recv(s,buf,len,flags);
if(ret<1&&strcmp(outbuf,""))
{
Out(outbuf,strlen(outbuf));
ZeroMemory( outbuf, sizeof(outbuf) ); //记得清空不然会一直发送
}
}
这样就ok了。