前言:游戏汉化中的一个重要的步骤是对资源的解包以及封包。虽然现在有很多软件可以实现解包的功能,但要将资源修改后再封包的话,不熟悉解密的算法是无法写出逆算法的。脚本是游戏中的一个最重要的资源,负责控制游戏引擎的行为以及存储了游戏的文本。今天我就以某个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;
……};
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!