如何制作植物大战僵尸可以为植物制作修改器
工具:SoftICE,金山游侠2002,VC++7.0,PE查看器,SPY++
测试平台:Window2000专业版SP2。
首先,我来介绍一下将要用到的工具:
1,SoftICE(不用说,我想你应该会用吧)
2.金山游侠2002(你应该也会用这个)
3、VC++7.0(不要求你会用,但至少要懂一个编程工具)
4、PE查看器(可以随便找一个,没有也没关系,我教你用SoftICE查看)
5.sp y++(VC中查看程序信息的工具,可以分享给他人,比如Delphi的WinSight32,C++Builder)。
接下来是你应该知道的:
1,装配基础
2,一些编程基础,至少你要懂几个我介绍的API函数。
3、PE文件结构基础,没关系,我给你讲解一下。
如果以上几点你都具备了,我们就可以开始了。
我来介绍一下我想教你的东西。大家一定都玩过PC游戏,所以你一定用过一些特殊的游戏修改器。比如暗黑破坏神、红色警戒、大富翁等经典游戏,都有自己特殊的修改器。请注意,我不是在谈论一般的修改工具,如FPE。
你试过用金山游侠修改红卫兵二代的钱吗?如果有,你要知道每次玩都要换,因为这个游戏是动态分配内存的,每次重新启动都会换。所以你会选择在网上下载一个专用修改器,那么你有没有想过自己做一个呢?想过吗?那你为什么不做呢?什么不会?那很容易处理。看完这个教程,你就会知道:D废话少说,让我来说说原理。
一些经常修改手游的朋友会知道,无论游戏中“物品”的内存地址是不是动态的,物品之间的距离都是不变的。我以《楚留香新传》为例。我先用金山游侠找内力值的内存地址,结果是:79F695C,然后“金创姚”的地址是:328D65438+。继续,我还没说完。现在再次运行游戏,找到内力值的地址,得到:798695C。然后找到金创耀的地址,地址是:321D1DC。两个值的内存地址都变了,但是你内力值的地址减去金创耀的地址是什么结果?没错,还是4769780,也就是说,无论这两个值的内存地址变成什么,它们之间的距离永远不会变,不仅在这个游戏里,在一般的游戏里,至少我没见过。没有:D
以上的事情总结出一个结论,就是只要我们得到这两个地址中的一个,就可以得到另一个,只要你知道它们之间的偏移量。
我们要做的第一步就是获取这个地址,但是内存中的地址是动态变化的,获取也没有用。这里我就教你把它静态化,让它永远不变!我就以《楚留香新传》为例。如果你有这次旅行,你可以和我一起去。如果没有,也没关系。理解这些步骤就行了。去工作吧!
首先进入游戏,找到内部值的地址,得到:798695C(不知道为什么这个上游每次重启都不改变内存地址),按Ctrl+D打开SoftICE,给出命令:BPM 798695C W(写这个地址时中断),返回游戏,打开角色属性面板,游戏中断。您将在Sofitece中看到以下说明:
0047 EB 17 moveax[EDX+000003 F4]指挥:D EDX+3F4会看到内力值。
0047EB1D推送EAX
………………………………
………………………………
从上面可以看出,0047EB17处的指令是将内力值的指针发送到EAX寄存器,这是典型的寻址方式。假设我们在EDX的基地址,那么每当我们用EDX+3F4的时候,我们就很容易得到内力值的地址,因为000003F4是一个常数,不会变,只有EDX的地址会变,所以只要。如果你还是不明白,请再读一遍。我们现在要做的是如何得到这个值,我来教你怎么做:
我的解决方案是设计一段代码,把EDX的值存储在一个地址里,然后运行这段代码,然后回到游戏原来的指令继续执行。什么?补丁技术?SMC?不管你说什么,只要它正常工作,一切都会好的:D
实际操作:
首先,在程序中找一个空白处存放我们设计的代码。这很简单。只要了解一些PE文件结构的朋友都会知道,通常在数据段的末尾会有一个缓冲区(。数据段)。我们可以在这个区域写任何东西。当然,你也可以用“90大法”找空白区域,但我还是推荐你用我教你的方法。如上所述,如果你没有PE文件查看工具,我可以教你用SoftICE查看,非常简单。只需给我一个命令:MAP32“模块名”,看我怎么做。
Ctrl+D调出SoftICE,然后给出命令:MAP32 CrhChs。这时,你应该看到EXE的每个部分的信息,我们只需要注意。数据部分。因为我们正在寻找数据段的结尾,所以我们将从下一段开始查找,如下所示:
。数据004FB000
。rsrc 00507000
的下一段。数据是。rsrc段,从00507000开始,也就是说基于00507000的下一个字节是数据段的结尾。我选择从00506950开始写代码。说了这么久,我们的代码是什么样子的?修改后的指令是什么?别急,请看下面:
修改0047EB17之后的代码:
0047EB17 JMP 00506950 //跳入我们的代码并执行它。
0047EB1C NOP //因为这条指令原来的长度是6字节,但是修改后的长度是5字节,所以补充了一条空指令。
0047EB1D推送EAX
//我们的代码:
00506950 MOV DWORD PTR EAX,[EDX+00003F4] //恢复我们中断的指令。
00506956 movdword ptr[0050691],EDX//在0050691保存EDX。
0050695C JMP 0047EB1D //返回原指令执行。
用SoftICE的A命令写上面的代码,OK!
现在我们来试试手术的效果。现在你用金山游侠搜索内力地址的地址。有什么变化?那是当场。如果不改变,我们还用这么努力吗?记下这个地址,回到游戏。Ctrl+D调出SoftICE,给出命令D *[00506961]+000003F4。您在数据窗口中看到了什么?呵呵,对,我看到了你刚记的地址,里面的值就是内力值。试着改变一下,回到游戏里,呵呵,内力值改变了:D
至此,我们已经完成了90%的工作,但也不要高兴得太早。后面的%10比前面的%90用的时间要长得多,因为我们要通过编程来实现,因为你不可能像刚才那样每次都做!
现在我来说说编程的步骤:
首先用FindWindow函数获取窗口句柄,然后用GetWindowThreadID函数从窗口句柄获取本进程的ID,再用OpenProcess获取进程的读写权限,最后用WriteProcessMemory和ReadProcessMemory读写内存。。。。呵呵,你的修饰语说完了:d。
下面是我之前复制的修改器源程序片段。第一部分是动态写刚才的代码,第二部分是读取和修改内力值。因为没时间整理测试,所以不能保证没有错误。如果你发现少了什么,你可以给我留言或者在QQ上给我写信。代码如下:
请注意几点:
1.一个字节一个字节写机器码。
2、注意先写好自己的代码,然后在巡讲中修改指令(下面的代码不这么做,因为不影响,但是要注意这个问题)
#定义MY_CODE5 0x00
#define MY_CODE6 0x90
//00506950
#define MY2_CODE1 0x8B
#define MY2_CODE2 0x82 //该部分是要编写的机器码的常量定义。
#定义MY2_CODE3 0xF4
#定义MY2_CODE4 0x03
#定义MY2_CODE5 0x00
#定义MY2_CODE6 0x00
#define MY3_CODE1 0x89
#define MY3_CODE2 0x15
#define MY3_CODE3 0x61
#定义MY3_CODE4 0x69
#define MY3_CODE5 0x50
#定义MY3_CODE6 0x00
#define MY4_CODE1 0xE9
#定义MY4_CODE2 0xBC
#define MY4_CODE3 0x81
#定义MY4_CODE4 0xF7
#定义MY4_CODE5 0xFF
// - //
DWORD a 1 = MY _ code 1;
DWORD A2 = MY _ CODE2
DWORD A3 = MY _ CODE3
DWORD A4 = MY _ CODE4
DWORD A5 = MY _ CODE5
DWORD A6 = MY _ CODE6
DWORD b 1 = MY2 _ code 1;
DWORD B2 = MY2 _ code 2;
DWORD B3 = MY2 _ code 3;//这部分是变量的定义。
DWORD B4 = MY2 _ CODE4
DWORD B5 = MY2 _ CODE5
DWORD B6 = MY2 _ code 6;
DWORD c 1 = MY3 _ code 1;
DWORD C2 = MY3 _ code 2;
DWORD C3 = MY3 _ code 3;
DWORD C4 = MY3 _ code 4;
DWORD C5 = MY3 _ CODE5
DWORD C6 = MY3 _ code 6;
DWORD d 1 = MY4 _ code 1;
DWORD D2 = MY4 _ code 2;
DWORD D3 = MY4 _ CODE3
DWORD D4 = MY4 _ code 4;
DWORD D5 = MY4 _ code 5;
// - //
HWND HWND =::FindWindow(" CRH class ",NULL);//获取窗口句柄
if(hWnd ==FALSE)
MessageBox("游戏没有运行!");
其他
{
GetWindowThreadProcessId(hWnd & amp;hProcId);//从窗口句柄获取进程ID
HANDLE nOK = open PROCESS(PROCESS _ ALL _ ACCESS | PROCESS _ termin ate | PROCESS _ VM _ OPERATION | PROCESS _ VM _ READ |
PROCESS_VM_WRITE,FALSE,hProcId);//打开进程并获得读取和权限。
if(nOK ==NULL)
MessageBox("打开进程时出错");
其他
{
//0047EB17
WriteProcessMemory(nOK,(LPVOID)0x 0047 EB 17;A1,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0047 EB 18;A2,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0047 EB 19;3,1,空);
WriteProcessMemory(nOK,(LPVOID)0x0047EB1A,& amp4,1,空);
WriteProcessMemory(nOK,(LPVOID)0x0047EB1B,& ampA5,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0047 EB 1C;6,1,空);
//00506950
WriteProcessMemory(nOK,(LPVOID)0x 00506950;B1,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506951;B2,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506952;B3,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506953;B4,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506954;B5,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506955;B6,1,空);
//第二句
WriteProcessMemory(nOK,(LPVOID)0x 00506956;C1,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506957;C2,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506958;C3,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506959;C4,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0050695 a;C5,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0050695 b;C6,1,空);
//最后一句
WriteProcessMemory(nOK,(LPVOID)0x 0050695 c;D1,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0050695d;D2,1,空);
WriteProcessMemory(nOK,(LPVOID)0x0050695E,& ampD3,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 0050695 f;D4,1,空);
WriteProcessMemory(nOK,(LPVOID)0x 00506960;D5,1,空);
close handle(nOK);//关闭进程句柄
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////
//读取并修改内力值
DWORD hProcId
HWND HWND =::FindWindow(" CRH class ",NULL);
if(hWnd ==FALSE)
MessageBox(" No ");
其他
{
GetWindowThreadProcessId(hWnd & amp;hProcId);
HANDLE nOK = open PROCESS(PROCESS _ ALL _ ACCESS | PROCESS _ termin ate | PROCESS _ VM _ OPERATION | PROCESS _ VM _ READ |
PROCESS_VM_WRITE,FALSE,hProcId);
if(nOK ==NULL)
MessageBox("ProcNo!");
其他
{
DWORD buf 1;
DWORD写;
BOOL OK=ReadProcessMemory(nOK,(LPCVOID)0x00506961,(LPVOID)amp;buf1,4,NULL);//阅读我们EDX的基础知识。
if(OK ==TRUE)
{
write = buf 1+0x 000003 f 4;//获取内力值的地址
DWORD Writeed = 0x00//要修改的值
BOOL B =WriteProcessMemory(nOK,(LPVOID)write,& ampWriteed,1,NULL);
如果(B = =假)
MessageBox(" write no ");
}
}
close handle(nOK);
}
啊,我对我写的东西麻木了。今天到此为止。知识不渊博难免会错过一些东西。请给我你的建议。如果我不会或者不喜欢用VC,你可以在QQ上和我交流。我可以教你如何用Delphi,C++Builder,Win32Asm或者VC实现以上功能。
(如转载此文,请不要更改内容和作者!)
作者:CrackYY
电子邮件:CoolYY@msn.com
OICQ:20651482
2001我从冯云了解到了IDA。看到他在了解凯撒的游戏资源,我觉得很好玩,我自己也开始学一些东西。当时一口气了解了一些游戏资源,当然都不是很复杂,主要是台湾省和日本的。
后来在首页放了一段时间,记得有不少感兴趣的朋友,就没来得及说了。现在来说说:)
工具当然是IDA+SoftIce。如果你想写自己的解压程序,你需要一个习惯编辑器。当然我用的是VC。
其实资源破解并不是很复杂,大致有三种方法。
1,硬裂纹
通过观察目标文件和反汇编代码,分析资源压缩或加密的格式,编写程序读取和修改文件,转换成你能识别的格式。
这是你自己了解资源时最容易想到的方法。
具体来说,通过一些特定的函数来确定游戏的资源求解函数,比如fopen、createFile等文件相关的函数,然后拼命分析汇编代码就可以了。
我之前的大部分资源都是这样破解的。最好先用UEDIT分析实际文件。有些格式太简单了。按照文件大小来看就行了。
这个方法,我解决过最复杂的就是魔幻传奇系列了。当时感觉更像GIF,但又不太一样。因为没有研究压缩算法,所以没有深究。但是后来在网上看到一篇文章说这是一个很常见的压缩算法,有一些解压工具可以解决。◎ #% ...真的很不愉快(不过还好我只花了几个小时就解决了游戏。
2、转储
图片加载后,直接从内存中导出。
这个方法也很容易想到。主要难点在于内存中资源的格式。对于3D游戏来说可能更容易。毕竟纹理渲染是显卡做的,不是软件。
我知道有人这样理解魔兽的资源,hook OpenGL的一些功能。
我通过这种方式学习了一些游戏文本(汉字),而对于赛车游戏,为了获取全部游戏文本,我特意说了游戏清零。
3、直接调用游戏的解码函数进行解码
类似于第二种方法,但是主动调用函数基本可以一次性解绑所有资源,不需要游戏通关。
当然不是让你调用游戏的解包模块。毕竟很多游戏都不是dll的形式。
我们只能侵入游戏进程内部,找一个合适的时间(一般是加载其他文件的时候,中断跳转,先完成我们的工作),调用内部函数,解锁所有资源。
我就这样解决了一个游戏。说起来,那个游戏的资源压缩率和rar差不多。
0.需求文档
LZW压缩算法是一种新颖的压缩方法,由莱姆普尔-齐夫-韦尔奇创立,并以他们的名字命名。它采用了高级的字符串表压缩,将每个第一个字符串放在一个字符串表中,并使用一个数字来表示该字符串。压缩后的文件只存储数字,不存储字符串,大大提高了文件的压缩效率。神奇的是,这个字符串表在压缩或解压缩时都能正确建立,压缩或解压缩后又被丢弃。
1.基本原则
首先建立一个字符串表,将每个第一次出现的字符串放入字符串表中,用一个数字表示,这个数字与字符串在字符串表中的位置有关,这个数字存储在一个压缩文件中。如果该字符串再次出现,它可以被代表它的数字替换,这个数字存储在文件中。压缩后丢弃字符串表。例如,如果“print”字符串在压缩过程中用266表示,那么每当它再次出现时就用266表示,并且“print”字符串将存储在字符串表中。当解码过程中遇到数字266时,可以从字符串表中找出266表示的字符串“print”,并根据解压缩时的压缩数据重新生成字符串表。
2.实现方法
A.初始化字符串表
在压缩信息时,我们首先要建立一个字符串表来记录每一个第一次出现的字符串。一个字符串表至少由两个字符数组组成,一个叫当前数组,一个叫前缀数组,因为一个文件中每个基本字符串的长度通常是2(但它所代表的实际字符串长度可以达到几百甚至几千),一个基本字符串由当前字符及其前面的字符(也叫前缀)组成。前缀数组存储的是字符串中的第一个字符,当前数组存储的是字符串中的最后一个字符,位置相同,所以只要确定一个下标,就可以确定它存储的基本字符串,所以在压缩数据时,用下标代替基本字符串。一般来说,字符串表的大小是4096字节(即2的12次方),这意味着一个字符串表最多可以存储4096个基本字符串。在初始化时,字符串表开头的所有字节都根据文件中的字符数被分配了编号。通常情况下,当前数组中的内容是元素的序号(即下标),比如第一个元素是0,第二个元素是1。如果字符数是256,应该初始化到第258个字节,这个字节里的值是257。其中数字256表示清除代码,数字257表示文件的结束代码。以下字节存储文件中第一次出现的字符串。还需要初始化concert前缀数组,数组中每个元素的值是任意的,但其位置一般是1,即每个元素的初始位置初始化为0XFF,初始化的元素个数与当前数组相同,后续的元素存放在每个第一次的字符串中。如果增加串表的长度,可以进一步提高压缩效率,但解码速度会降低。
B.压缩法
要了解压缩方法,首先要了解几个名词,一是字符流,二是码流,三是当前码,四是当前前缀。字符流是源文件中未压缩的文件数据;码流是压缩后写入文件的压缩文件数据;当前代码是刚从字符流中读入的字符;当前前缀是刚刚读到的字符前面的字符。
压缩文件时,不管文件中有多少个字符,颜色值都要以字节为单位放入码流中,每个字节代表一种颜色。虽然在源文件中用一个字节表示16色、四色和双色时会浪费四位或更多位(因为一个字节中的四位可以表示16色),但在使用LZW压缩方法时,字节中的空闲位是可以恢复的。压缩时,先从字符流中读取第一个字符作为当前前缀,然后取第二个字符作为当前码,当前前缀和当前码组成第一个基本字符串(如果当前前缀为A,当前码为B,则此字符串为AB),查字符串表,此时肯定找不到相同的字符串,然后将此字符串写入字符串表, 将当前前缀放入前缀数组,将当前代码放入当前数组,并将当前前缀发送到代码流。 将当前代码放入当前前缀,然后读取下一个字符,即当前代码。这时就形成了一个新的基本串(如果当前代码是C,这个基本串就是BC)。查找字符串表。如果有这个字符串,则丢弃当前前缀中的值,将这个字符串在字符串表中的位置代码(即下标)作为当前前缀,然后读取下一个字符作为当前代码,形成新的基本字符串。可以看出,在压缩过程中,前缀数组中的值就是码流中的字符,大于字符数的码肯定代表一个字符串,小于等于字符数的码就是字符本身。
C.清除代码
事实上,压缩一个文件时,往往需要多次初始化字符串表。通常,第一次出现在文件中的基本字符串的数量会超过4096。在压缩过程中,只要字符串长度超过4096,就需要将当前前缀和当前代码输入到码流中,并在码流中添加一个清零代码初始化字符串表,按照上述方法继续压缩。
D.结束代码
当所有压缩完成后,一个文件结束码输出到码流中,其值为字符数加1。在256色文件中,结束代码是257。
E.字节空间回收
文件输出的码流中的数据以数据包的形式存储,所有代码都以单元存储,有效节省存储空间。这就像一个4位彩色(16色)文件。以字节存储时,只能使用4位,其他4位都浪费了。以位存储时,每个字节可以存储两种颜色代码。实际上,在文件中,使用了变量存储方法。从压缩过程中可以看出,字符串表前缀数组中每个元素的值都是有规律的。在256色文件中,258-511元素中的值的范围是0-510,可以用9位二进制数表示。512-1023元素的取值范围是0-1022,可以用二进制数10来表示,1024-2047元素的取值范围是0-2046,正好是165438。存储可变位数的代码时,基数为文件中的字符数加1,位数随着代码数的增加而增加,直到位数超过12(此时字符串表中的字符串数正好是12的2次方,即4096)。基本方法是:码流中每增加一个字符,就要判断该字符所在字符串在字符串表中的位置(即下标)是否超过当前的位数幂2,一旦超过当前的位数幂,位数就增加1。例如,在4位文件中,初始代码存储为5位,第一个代码放在第一个字节的低5位,第二个代码的低3位放在高3位,第二个字节的低2位放在第二个代码的高2位,依此类推。对于一个8位(256色)文件,位数的基数是9,一个代码至少应该放在两个字节中。
F.压缩范围
下面是文件编码的一个例子。如果你留心的话,会发现这是一种很奇妙的编码方式,以及为什么压缩后不再需要字符串表,解码时可以根据码流信息重新创建字符串表。
字符串:1,2,1,1,1,1,2,3,4,1,2,3,4,5,9,…
当前缀:21,1,1,1,2,3,4,1,2,3,4,5,9,…
当前前缀:1,2,1,1,260,1,258,3,4,1,258,262,4,5,…
当前数组:2,1,1,1,3,4,1,4,5,9,…
数组下标:258,259,260,261,262,263,264,265,266,267,…
码流:1,2,1,260,258,3,4,262,4,5,…
3.测试文档
描述:
选择时,请选择1-3的数据。如果选择其他数据,将会出现错误。
使用文件
进入程序后,选择是压缩、解压还是退出程序。
压缩文件:
1)提示:“输入文件名?”输入:d: \ cc \ test.txt。
2)提示:“压缩文件名?”输入:test.lzw
3)显示:“正在压缩………”“*”表示文件压缩的进度。
注意:如果输入的文件不存在,将重复提示,直到输入正确的文件位置和文件名。生成的test.lzw将存储在程序所在的根目录下。
例如,如果程序放在D:\cc\中,生成的文件也在D:\cc\中。
解压:
1)提示:“输入文件名?”输入:test.lzw
2)提示:“压缩文件名?”输入:test.txt
3)显示:“展开…………”“*”表示文件解压缩的进度。
注意:如果输入的文件不存在,将重复提示,直到输入正确的文件位置和文件名。生成的test.lzw将存储在程序所在的根目录下。
Ani (Applicedon Startins沙漏)文件是MS-Windows的一个动画光标文件,其文件扩展名为“.阿尼”。它一般由四部分组成:文本描述区、信息区、时间控制区和数据区,即一个列表块。Anih块、速率块和列表块。
下面以ANI文件的文件内容(数据e)和标准结构图为例(图):
1.丛(0000-006D)是WND0WS95 & NTANI文件的文本描述区。
如果你想为你开发的ANI文件提供一点文字描述,并添加你的版权信息,同时要有ANI文件播放软件的识别,这是你唯一的选择。如果觉得麻烦或者没什么可写的,可以把这个块里的内容全部去掉,把块的大小设为0。记住,“块ID”
“a列表”和“块大小”为12字节,不得更改、移动或删除,否则后果自负。
也许是为了将文本描述信息系统化,ACONLIST块内部有几个子块。本例中使用的两个块是:INFOINAM块(提供本文档的解释)和IART块(用于插入版本信息)。说实话,你可以用在AVI文件中插入自定义块的方法来添加自己的自定义块,结果只是被ANI播放软件当成了“垃圾”。
0000-0003:多媒体文件ID: RIFF
0004-0007;文件大小(2052小时字节)-8字节
0008- 000F:列表块标识符,标记文本描述区的开始。
0010-0013:列表块的大小(5Ah字节)
0014-001b: Infoinam块标识符,标记文件描述中信息子块的开始。
001C-001C-001F:info inam块大小(20h字节)
0020-003F:文件描述信息子块的内容是“应用启动沙漏”。
0040-0043: IART块识别码,它标志着版权描述信息的开始。
0044-0047: IART块大小(26h字节)
0048- 006D:版权信息在“微软公司,版权1995”块的内容中。
2.从(006E-0099)?