首页
社区
课程
招聘
[原创]某游戏的脚本解密算法逆向
发表于: 2009-4-29 12:43 16041

[原创]某游戏的脚本解密算法逆向

2009-4-29 12:43
16041

前言:游戏汉化中的一个重要的步骤是对资源的解包以及封包。虽然现在有很多软件可以实现解包的功能,但要将资源修改后再封包的话,不熟悉解密的算法是无法写出逆算法的。脚本是游戏中的一个最重要的资源,负责控制游戏引擎的行为以及存储了游戏的文本。今天我就以某个galgame为例说说我逆向的过程,高手随便看看就好了。游戏的名字是《俺たちに翼はない》,可以在网上找到下载。

首先是老规矩,对CreateFileA下段,来到下面的地方:
00473839   > \6A 00         PUSH 0                                   ; /hTemplateFile = NULL
0047383B   .  68 80000000   PUSH 80                                  ; |Attributes = NORMAL
00473840   .  6A 03         PUSH 3                                   ; |Mode = OPEN_EXISTING
00473842   .  6A 00         PUSH 0                                   ; |pSecurity = NULL
00473844   .  6A 01         PUSH 1                                   ; |ShareMode = FILE_SHARE_READ
00473846   .  8D95 D0FEFFFF LEA EDX,DWORD PTR SS:[EBP-130]           ; |
0047384C   .  68 00000080   PUSH 80000000                            ; |Access = GENERIC_READ
00473851   .  52            PUSH EDX                                 ; |FileName= "I:\Program Files\Navel\壌偨偪偵梼偼側偄\SCRIPT.LPK"
00473852   .  FF15 B0D14E00 CALL DWORD PTR DS:[<&KERNEL32.CreateFile>; \CreateFileA
00473858   .  8BD8          MOV EBX,EAX
0047385A   .  83FB FF       CMP EBX,-1                               ;  判断打开是否成功,句柄为-1则不跳
0047385D   .  895D E0       MOV DWORD PTR SS:[EBP-20],EBX            ;  保存句柄,我的机器上句柄为90
00473860   .  75 18         JNZ SHORT ORE_TUBA.0047387A              ;  句柄有效则跳转
00473862   .  B8 03000000   MOV EAX,3
00473867   .  8B4D F4       MOV ECX,DWORD PTR SS:[EBP-C]
0047386A   .  64:890D 00000>MOV DWORD PTR FS:[0],ECX
00473871   .  5F            POP EDI
00473872   .  5E            POP ESI
00473873   .  5B            POP EBX
00473874   .  8BE5          MOV ESP,EBP
00473876   .  5D            POP EBP
00473877   .  C2 1400       RETN 14
0047387A   >  8B07          MOV EAX,DWORD PTR DS:[EDI]
0047387C   .  56            PUSH ESI                                 ;  文件名“SCRIPT.LPK"
0047387D   .  8BCF          MOV ECX,EDI
0047387F   .  FF50 5C       CALL DWORD PTR DS:[EAX+5C]               ;  处理字符串的函数,将文件名转换为大写,解密的时候要用到
00473882   .  85C0          TEST EAX,EAX                             ;  判断是否成功
00473884   .  74 52         JE SHORT ORE_TUBA.004738D8
00473886   .  8B10          MOV EDX,DWORD PTR DS:[EAX]
00473888   .  8D4D 08       LEA ECX,DWORD PTR SS:[EBP+8]
0047388B   .  51            PUSH ECX
0047388C   .  8D8D D0FEFFFF LEA ECX,DWORD PTR SS:[EBP-130]
00473892   .  53            PUSH EBX
00473893   .  51            PUSH ECX
00473894   .  8BC8          MOV ECX,EAX
00473896   .  FF52 04       CALL DWORD PTR DS:[EDX+4]                ;  重要函数,跟进去看看
00473899   .  85C0          TEST EAX,EAX
0047389B   .  8945 E8       MOV DWORD PTR SS:[EBP-18],EAX

运气不错,第一次断下就是要打开脚本文件“SCRIPT.LPK”了。一直跟到call 00485D20这个地方,有点可疑,跟进去看看。
00485D20   .  55            PUSH EBP
00485D21   .  8BEC          MOV EBP,ESP
00485D23   .  6A FF         PUSH -1
00485D25   .  68 EBB44E00   PUSH ORE_TUBA.004EB4EB                   ;  SE 处理程序安装
00485D2A   .  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
00485D30   .  50            PUSH EAX
00485D31   .  64:8925 00000>MOV DWORD PTR FS:[0],ESP
00485D38   .  81EC 54020000 SUB ESP,254
00485D3E   .  8B45 0C       MOV EAX,DWORD PTR SS:[EBP+C]             ;  取句柄
00485D41   .  53            PUSH EBX
00485D42   .  56            PUSH ESI
00485D43   .  8B75 08       MOV ESI,DWORD PTR SS:[EBP+8]
00485D46   .  57            PUSH EDI
00485D47   .  33FF          XOR EDI,EDI
00485D49   .  83F8 FF       CMP EAX,-1                               ;  判断句柄是否有效
00485D4C   .  8965 F0       MOV DWORD PTR SS:[EBP-10],ESP
00485D4F   .  8945 E4       MOV DWORD PTR SS:[EBP-1C],EAX
00485D52   .  897D D0       MOV DWORD PTR SS:[EBP-30],EDI
00485D55   .  897D FC       MOV DWORD PTR SS:[EBP-4],EDI
00485D58   .  75 4D         JNZ SHORT ORE_TUBA.00485DA7              ;  句柄有效则跳转
00485D5A   .  3BF7          CMP ESI,EDI
00485D5C   .  75 15         JNZ SHORT ORE_TUBA.00485D73
00485D5E   .  8D45 A8       LEA EAX,DWORD PTR SS:[EBP-58]
00485D61   .  68 68604F00   PUSH ORE_TUBA.004F6068                   ; /Arg2 = 004F6068
00485D66   .  50            PUSH EAX                                 ; |Arg1
00485D67   .  C745 A8 05000>MOV DWORD PTR SS:[EBP-58],5              ; |
00485D6E   .  E8 1F5F0500   CALL ORE_TUBA.004DBC92                   ; \ORE_TUBA.004DBC92
00485D73   >  57            PUSH EDI                                 ; /hTemplateFile
00485D74   .  68 80000000   PUSH 80                                  ; |Attributes = NORMAL
00485D79   .  6A 03         PUSH 3                                   ; |Mode = OPEN_EXISTING
00485D7B   .  57            PUSH EDI                                 ; |pSecurity
00485D7C   .  6A 01         PUSH 1                                   ; |ShareMode = FILE_SHARE_READ
00485D7E   .  68 00000080   PUSH 80000000                            ; |Access = GENERIC_READ
00485D83   .  56            PUSH ESI                                 ; |FileName
00485D84   .  FF15 B0D14E00 CALL DWORD PTR DS:[<&KERNEL32.CreateFile>; \CreateFileA
00485D8A   .  83F8 FF       CMP EAX,-1
00485D8D   .  8945 E4       MOV DWORD PTR SS:[EBP-1C],EAX
00485D90   .  75 15         JNZ SHORT ORE_TUBA.00485DA7
00485D92   .  8D4D B4       LEA ECX,DWORD PTR SS:[EBP-4C]
00485D95   .  68 68604F00   PUSH ORE_TUBA.004F6068                   ; /Arg2 = 004F6068
00485D9A   .  51            PUSH ECX                                 ; |Arg1
00485D9B   .  C745 B4 03000>MOV DWORD PTR SS:[EBP-4C],3              ; |
00485DA2   .  E8 EB5E0500   CALL ORE_TUBA.004DBC92                   ; \ORE_TUBA.004DBC92
00485DA7   >  8B4D E4       MOV ECX,DWORD PTR SS:[EBP-1C]            ;  取句柄
00485DAA   .  8D55 EC       LEA EDX,DWORD PTR SS:[EBP-14]            ;  缓冲区
00485DAD   .  57            PUSH EDI                                 ; /pOverlapped
00485DAE   .  52            PUSH EDX                                 ; |pBytesRead
00485DAF   .  8D45 C8       LEA EAX,DWORD PTR SS:[EBP-38]            ; |
00485DB2   .  6A 08         PUSH 8                                   ; |BytesToRead = 8
00485DB4   .  50            PUSH EAX                                 ; |Buffer
00485DB5   .  51            PUSH ECX                                 ; |hFile
00485DB6   .  FF15 3CD24E00 CALL DWORD PTR DS:[<&KERNEL32.ReadFile>] ; \ReadFile
00485DBC   .  85C0          TEST EAX,EAX                             ;  测试是否成功
00485DBE   .  75 15         JNZ SHORT ORE_TUBA.00485DD5              ;  不为0则跳
00485DC0   .  8D55 AC       LEA EDX,DWORD PTR SS:[EBP-54]
00485DC3   .  68 68604F00   PUSH ORE_TUBA.004F6068                   ; /Arg2 = 004F6068
00485DC8   .  52            PUSH EDX                                 ; |Arg1
00485DC9   .  C745 AC 07000>MOV DWORD PTR SS:[EBP-54],7              ; |
00485DD0   .  E8 BD5E0500   CALL ORE_TUBA.004DBC92                   ; \ORE_TUBA.004DBC92
00485DD5   >  817D C8 4C504>CMP DWORD PTR SS:[EBP-38],314B504C       ;  跳到这里,判断文件开头四个字节是否”LPK1“
00485DDC   .  74 15         JE SHORT ORE_TUBA.00485DF3               ;  是的话跳转进行处理
00485DDE   .  8D45 C4       LEA EAX,DWORD PTR SS:[EBP-3C]
00485DE1   .  68 68604F00   PUSH ORE_TUBA.004F6068                   ; /Arg2 = 004F6068
00485DE6   .  50            PUSH EAX                                 ; |Arg1
00485DE7   .  C745 C4 01000>MOV DWORD PTR SS:[EBP-3C],1              ; |
00485DEE   .  E8 9F5E0500   CALL ORE_TUBA.004DBC92                   ; \ORE_TUBA.004DBC92
00485DF3   >  8D8D A0FEFFFF LEA ECX,DWORD PTR SS:[EBP-160]
00485DF9   .  6A 01         PUSH 1
00485DFB   .  51            PUSH ECX
00485DFC   .  56            PUSH ESI
00485DFD   .  E8 0EEEFEFF   CALL ORE_TUBA.00474C10                   ;  此处也是一个字符串复制的函数,经过一串比较后复制了”SCRIPT.LPK"字符串,但具体作用未知- -
00485E02   .  83C4 0C       ADD ESP,0C                               ;  平衡堆栈
00485E05   .  8D95 A0FDFFFF LEA EDX,DWORD PTR SS:[EBP-260]
00485E0B   .  8D85 A0FEFFFF LEA EAX,DWORD PTR SS:[EBP-160]
00485E11   .  52            PUSH EDX
00485E12   .  50            PUSH EAX
00485E13   .  E8 08F0FEFF   CALL ORE_TUBA.00474E20                   ;  又是处理文件名的函数,去掉"SCRIPT"的后缀名
00485E18   .  83C4 08       ADD ESP,8                                ;  平衡堆栈
00485E1B   .  8D8D A0FEFFFF LEA ECX,DWORD PTR SS:[EBP-160]
00485E21   .  51            PUSH ECX                                 ; /StringOrChar
00485E22   .  FF15 58D34E00 CALL DWORD PTR DS:[<&USER32.CharUpperA>] ; \CharUpperA
00485E28   .  8D95 A0FEFFFF LEA EDX,DWORD PTR SS:[EBP-160]           ;  取“SCRIPT”的地址
00485E2E   .  52            PUSH EDX                                 ; /String
00485E2F   .  FF15 FCD04E00 CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \lstrlenA
00485E35   .  8945 E0       MOV DWORD PTR SS:[EBP-20],EAX
00485E38   .  8D8405 9FFEFF>LEA EAX,DWORD PTR SS:[EBP+EAX-161]
00485E3F   .  8D8D A0FEFFFF LEA ECX,DWORD PTR SS:[EBP-160]
00485E45   .  C745 DC 6BACB>MOV DWORD PTR SS:[EBP-24],A5B9AC6B
00485E4C   .  C745 D4 E59D6>MOV DWORD PTR SS:[EBP-2C],9A639DE5
00485E53   .  8945 B0       MOV DWORD PTR SS:[EBP-50],EAX
00485E56   .  894D BC       MOV DWORD PTR SS:[EBP-44],ECX
00485E59   .  8B45 DC       MOV EAX,DWORD PTR SS:[EBP-24]
00485E5C   .  8B5D D4       MOV EBX,DWORD PTR SS:[EBP-2C]
00485E5F   .  8B75 B0       MOV ESI,DWORD PTR SS:[EBP-50]
00485E62   .  8B7D BC       MOV EDI,DWORD PTR SS:[EBP-44]
00485E65   .  8B4D E0       MOV ECX,DWORD PTR SS:[EBP-20]            ;  上述都是一些初始化步骤,下面开始是第一个解密算法
00485E68   >  3206          XOR AL,BYTE PTR DS:[ESI]
00485E6A   .  321F          XOR BL,BYTE PTR DS:[EDI]
00485E6C   .  C1C8 07       ROR EAX,7
00485E6F   .  83C7 01       ADD EDI,1
00485E72   .  83EE 01       SUB ESI,1
00485E75   .  C1C3 07       ROL EBX,7
00485E78   .  49            DEC ECX
00485E79   .^ 75 ED         JNZ SHORT ORE_TUBA.00485E68              ;  循环6次
00485E7B   .  8945 EC       MOV DWORD PTR SS:[EBP-14],EAX            ;  保存eax,值为0xA8E7FAF1
00485E7E   .  895D D8       MOV DWORD PTR SS:[EBP-28],EBX            ;  保存ebx,值为0xA742F274
这里就是读取SCRIPT.LPK的内容然后进行判断了。首先从文件中读取8个字节,然后比较前面四个是否为“LPK1”,不是的话退出,是的话对密钥进行解密运算,那个算法写成C代码如下:

#include<stdio.h>
#define ror(m,n) (m>>n)|(m<<(8*sizeof(int)-n))
#define rol(m,n) (m<<n)|(m>>(8*sizeof(int)-n))

main()
{
	char name[]="SCRIPT";
	unsigned int a=0xa5b9ac6b,b=0x9a639de5;
	printf("0x%08x  0x%02x\n",a,b);
	for(int i=0,j=5;i<6;i++,j--)
	{
		*(char*)&a^=name[j];
		*(char*)&b^=name[i];
		a=ror(a,7);
		b=rol(b,7);
		printf("0x%08x  0x%08x\n",a,b);
	}
	printf("0x%08x  0x%08x\n",a,b);
}
int data[]//缓冲区地址
int n=0x31746285;
int key=0xa742f274;
for(i=0;i<0x3f03/4;i++)
{
	n=rol(n,4);
	data[i]^=key;
	key=ror(key,*(char*)&n);
}
struct header{
	DWORD numberoffile;
	BYTE unknow[2];
	DWORD offset;
……};

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (12)
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
很不錯的文章,之前為了破hgame得到裡面的圖片時也有研究過,因為多數的圖片都有加密,所以也很麻煩,不過其實只要了解createfile,readfile,setfilepointer,然後對read出的資料做斷點分析,一些簡單點的圖片也能解出,順便問一下樓主有沒有分析過戰女神zero這遊戲
2009-4-29 15:44
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
精彩文章 努力学习中
2009-4-29 15:51
0
雪    币: 445
活跃值: (25)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
4
战女神我没下,我是一个宿舍公用1m的带宽,所以很多游戏都忘记下了orz

关于PC游戏资源的加密的资料实在太少了,网上的几乎都是掌机或者TV GAME的游戏破解。而且pc游戏对资源的加密算法是乱七八糟,很多时候只能自己用olldbg来调试,因此我就将我调试时候的想法以及思路写出来,好让那些跟我一样的菜鸟少走些弯路
2009-4-29 18:14
0
雪    币: 214
活跃值: (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
天龙八部用的是OGRE引擎,为了直接读写资源包,逆向过,那个貌似比你这个还简单,olldbg一下Launch.exe,爆启动Launch.bin,createfile后就发现所有相关函数都在一块了
2009-4-29 20:38
0
雪    币: 214
活跃值: (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
说错了,天龙八部不是简单一点,是简单太多了,压根没有加密,就一个hash函数
2009-4-29 20:41
0
雪    币: 254
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
不给自己找理由,虽然初学,但是也支持你下··
2009-4-30 08:56
0
雪    币: 8227
活跃值: (3376)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
看不懂的一律收藏以后再看
2009-4-30 10:34
0
雪    币: 226
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
分析的很清晰,学习了
2009-4-30 11:23
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
嗯...调试思路很清晰啊...学习了
国内PC游戏汉化喜欢把技术藏着掖着.支持LZ这样的Share精神
2009-5-4 12:57
0
雪    币: 97697
活跃值: (200844)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
11
Support.
2009-5-4 13:44
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
收藏了,刚接触这个
2009-5-4 13:48
0
雪    币: 261
活跃值: (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13
学习了!
2009-5-15 23:03
0
游客
登录 | 注册 方可回帖
返回
//