前言:游戏汉化中的一个重要的步骤是对资源的解包以及封包。虽然现在有很多软件可以实现解包的功能,但要将资源修改后再封包的话,不熟悉解密的算法是无法写出逆算法的。脚本是游戏中的一个最重要的资源,负责控制游戏引擎的行为以及存储了游戏的文本。
首先是老规矩,对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);
}
但从文件中读取的后四个字节还不知道有什么用,先不管。下面继续单步跟过去:
00485E81 . 6A 3C PUSH 3C
00485E83 . E8 155B0500 CALL ORE_TUBA.004DB99D ; 分配一段内存,从下面的代码来看是一个结构,与解密的算法关系不大
00485E88 . 8BC8 MOV ECX,EAX
00485E8A . 83C4 04 ADD ESP,4
00485E8D . 894D DC MOV DWORD PTR SS:[EBP-24],ECX
00485E90 . 85C9 TEST ECX,ECX
00485E92 . C645 FC 01 MOV BYTE PTR SS:[EBP-4],1
00485E96 . 74 09 JE SHORT ORE_TUBA.00485EA1 ; 判断上面分配内存是否成功,失败则跳转
00485E98 . E8 A3010000 CALL ORE_TUBA.00486040 ; 上面分配的内存的初始化代码
00485E9D . 8BF8 MOV EDI,EAX
00485E9F . EB 02 JMP SHORT ORE_TUBA.00485EA3
00485EA1 > 33FF XOR EDI,EDI
00485EA3 > 85FF TEST EDI,EDI
00485EA5 . C645 FC 00 MOV BYTE PTR SS:[EBP-4],0
00485EA9 . 897D D0 MOV DWORD PTR SS:[EBP-30],EDI
00485EAC . 75 15 JNZ SHORT ORE_TUBA.00485EC3
00485EAE . 8D55 A4 LEA EDX,DWORD PTR SS:[EBP-5C]
00485EB1 . 68 68604F00 PUSH ORE_TUBA.004F6068 ; /Arg2 = 004F6068
00485EB6 . 52 PUSH EDX ; |Arg1
00485EB7 . C745 A4 08000>MOV DWORD PTR SS:[EBP-5C],8 ; |
00485EBE . E8 CF5D0500 CALL ORE_TUBA.004DBC92 ; \ORE_TUBA.004DBC92
00485EC3 > A1 483F5000 MOV EAX,DWORD PTR DS:[503F48]
00485EC8 . 8B75 EC MOV ESI,DWORD PTR SS:[EBP-14]
00485ECB . 33C6 XOR EAX,ESI
00485ECD . 8947 14 MOV DWORD PTR DS:[EDI+14],EAX
00485ED0 . 8B45 D8 MOV EAX,DWORD PTR SS:[EBP-28]
00485ED3 . 8B15 503F5000 MOV EDX,DWORD PTR DS:[503F50]
00485ED9 . 8B75 CC MOV ESI,DWORD PTR SS:[EBP-34] ; [ebp-34]存的就是刚才从SCRIPT.LPK中读的第二个双字的内容
00485EDC . 33C2 XOR EAX,EDX
00485EDE . 33F0 XOR ESI,EAX ;与0xA742F274异或
00485EE0 . 8945 D8 MOV DWORD PTR SS:[EBP-28],EAX
00485EE3 . 8BC6 MOV EAX,ESI
00485EE5 . 81E6 FFFFFF00 AND ESI,0FFFFFF ;舍去高四位
00485EEB . C1E8 18 SHR EAX,18
00485EEE . 8845 EA MOV BYTE PTR SS:[EBP-16],AL
00485EF1 . 24 01 AND AL,1
00485EF3 . 8975 E0 MOV DWORD PTR SS:[EBP-20],ESI
00485EF6 . 8845 EB MOV BYTE PTR SS:[EBP-15],AL
00485EF9 . 74 09 JE SHORT ORE_TUBA.00485F04
00485EFB . C1E6 0B SHL ESI,0B
00485EFE . 83EE 08 SUB ESI,8
00485F01 . 8975 E0 MOV DWORD PTR SS:[EBP-20],ESI
00485F04 > 56 PUSH ESI ; esi=将要从文件读取的大小
00485F05 . E8 935A0500 CALL ORE_TUBA.004DB99D ; 分配空间
00485F0A . 83C4 04 ADD ESP,4 ; 平衡堆栈
00485F0D . 8947 1C MOV DWORD PTR DS:[EDI+1C],EAX
00485F10 . 85C0 TEST EAX,EAX
00485F12 . 75 15 JNZ SHORT ORE_TUBA.00485F29
00485F14 . 8D4D C0 LEA ECX,DWORD PTR SS:[EBP-40]
00485F17 . 68 68604F00 PUSH ORE_TUBA.004F6068 ; /Arg2 = 004F6068
00485F1C . 51 PUSH ECX ; |Arg1
00485F1D . C745 C0 08000>MOV DWORD PTR SS:[EBP-40],8 ; |
00485F24 . E8 695D0500 CALL ORE_TUBA.004DBC92 ; \ORE_TUBA.004DBC92
00485F29 > 8D55 EC LEA EDX,DWORD PTR SS:[EBP-14]
00485F2C . 6A 00 PUSH 0 ; /pOverlapped = NULL
00485F2E . 52 PUSH EDX ; |pBytesRead
00485F2F . 56 PUSH ESI ; |BytesToRead
00485F30 . 50 PUSH EAX ; |Buffer=0x00AE4008,记下这个地址
00485F31 . 8B45 E4 MOV EAX,DWORD PTR SS:[EBP-1C] ; |
00485F34 . 50 PUSH EAX ; |hFile
00485F35 . FF15 3CD24E00 CALL DWORD PTR DS:[<&KERNEL32.ReadFile>] ; \ReadFile
00485F3B . 85C0 TEST EAX,EAX ; 判断是否成功
00485F3D . 75 15 JNZ SHORT ORE_TUBA.00485F54 ; 成功则跳转
00485F3F . 8D4D B8 LEA ECX,DWORD PTR SS:[EBP-48]
00485F42 . 68 68604F00 PUSH ORE_TUBA.004F6068 ; /Arg2 = 004F6068
00485F47 . 51 PUSH ECX ; |Arg1
00485F48 . C745 B8 07000>MOV DWORD PTR SS:[EBP-48],7 ; |
00485F4F . E8 3E5D0500 CALL ORE_TUBA.004DBC92 ; \ORE_TUBA.004DBC92
00485F54 > 8B57 1C MOV EDX,DWORD PTR DS:[EDI+1C]
00485F57 . C745 D4 85627>MOV DWORD PTR SS:[EBP-2C],31746285
00485F5E . 8955 DC MOV DWORD PTR SS:[EBP-24],EDX
00485F61 . 8B45 D8 MOV EAX,DWORD PTR SS:[EBP-28] ; eax=前面算出来的第二个解密算子
00485F64 . 8B4D D4 MOV ECX,DWORD PTR SS:[EBP-2C] ; ecx=常数,用于解密
00485F67 . 8B5D E0 MOV EBX,DWORD PTR SS:[EBP-20] ; ebx=缓冲区大小
00485F6A . 8B75 DC MOV ESI,DWORD PTR SS:[EBP-24] ; esi=缓冲区地址
00485F6D . C1EB 02 SHR EBX,2 ; 这里到下面的跳转都是对缓冲区的解密换算
00485F70 > C1C1 04 ROL ECX,4
00485F73 . 3106 XOR DWORD PTR DS:[ESI],EAX
00485F75 . D3C8 ROR EAX,CL
00485F77 . 83C6 04 ADD ESI,4
00485F7A . 4B DEC EBX
00485F7B .^ 75 F3 JNZ SHORT ORE_TUBA.00485F70 ;跳回去继续循环
00485F7D . 8A45 EB MOV AL,BYTE PTR SS:[EBP-15]
00485F80 . 8B12 MOV EDX,DWORD PTR DS:[EDX] ; [EDX]为解密后的第一个双字的内容,为0x1a2,会是啥呢,我猜测是脚本文件的个数
看到了没?[EBP-34]中存储的就是从文件中读出的第二个双字,将它与上面的算法算出来的b相异或就得到0x3f03,然后可以看出分配了0x3f03个字节的内存空间,再从文件中读出0x3f03个字节的内容。然后对读出的内容进行解密,写成C代码如下:
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);
}
后面还有一段读取缓冲区的操作:
0048621F . 8B3E MOV EDI,DWORD PTR DS:[ESI] ; 读取缓冲区的数据,这个是什么,下面就知道
00486221 . 83C6 04 ADD ESI,4
00486224 . 84C0 TEST AL,AL
00486226 . 8973 20 MOV DWORD PTR DS:[EBX+20],ESI
00486229 . 74 1A JE SHORT ORE_TUBA.00486245
0048622B . 8D4D EC LEA ECX,DWORD PTR SS:[EBP-14]
0048622E . 8D95 D0FEFFFF LEA EDX,DWORD PTR SS:[EBP-130]
00486234 . 51 PUSH ECX
00486235 . 52 PUSH EDX
00486236 . 56 PUSH ESI
00486237 . 8BCB MOV ECX,EBX
00486239 . C745 EC FFFFF>MOV DWORD PTR SS:[EBP-14],-1
00486240 . E8 BB020000 CALL ORE_TUBA.00486500
00486245 > 03F7 ADD ESI,EDI ; 将前面读出的双子与缓冲区偏移地址相加,再保存起来
00486247 > 8A45 10 MOV AL,BYTE PTR SS:[EBP+10]
0048624A . 8973 2C MOV DWORD PTR DS:[EBX+2C],ESI
再看看数据窗口中缓冲区的数据:
00AE4008 A2 01 00 00 00 00 BF 29 00 00 ?....?..
a
可以看出第一个双字是疑似脚本文件个数,中间两个字节作用未知,然后一个双字是一个偏移,用结构可以表达如下:
struct header{
DWORD numberoffile;
BYTE unknow[2];
DWORD offset;
……};
这个函数剩下的地方都没什么猛料,回到原来的函数后继续跟发现都是一些字符串的处理,一直跟到下面地址为0x004750eb的一个call,看到压栈的参数有一个为字符串“SCRIPT/LL_APP.SYS”,跟进去看看:
00472ED0 . 55 PUSH EBP
00472ED1 . 8BEC MOV EBP,ESP
00472ED3 . 6A FF PUSH -1
00472ED5 . 68 C0AB4E00 PUSH ORE_TUBA.004EABC0 ; SE 处理程序安装
00472EDA . 64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
00472EE0 . 50 PUSH EAX
00472EE1 . 64:8925 00000>MOV DWORD PTR FS:[0],ESP
00472EE8 . 81EC 44020000 SUB ESP,244
00472EEE . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
00472EF1 . 53 PUSH EBX
00472EF2 . 56 PUSH ESI
00472EF3 . 57 PUSH EDI
00472EF4 . 8BD9 MOV EBX,ECX
00472EF6 . 8965 F0 MOV DWORD PTR SS:[EBP-10],ESP
00472EF9 . 6A 00 PUSH 0
00472EFB . 8D8D B4FEFFFF LEA ECX,DWORD PTR SS:[EBP-14C]
00472F01 . 50 PUSH EAX
00472F02 . 51 PUSH ECX
00472F03 . 895D E4 MOV DWORD PTR SS:[EBP-1C],EBX
00472F06 . C745 E8 FFFFF>MOV DWORD PTR SS:[EBP-18],-1
00472F0D . C745 FC 00000>MOV DWORD PTR SS:[EBP-4],0
00472F14 . E8 A71A0000 CALL ORE_TUBA.004749C0 ; 处理字符串
00472F19 . 83C4 0C ADD ESP,0C
00472F1C . 8945 EC MOV DWORD PTR SS:[EBP-14],EAX
00472F1F . 85C0 TEST EAX,EAX
00472F21 . 75 15 JNZ SHORT ORE_TUBA.00472F38
00472F23 . 8D55 CC LEA EDX,DWORD PTR SS:[EBP-34]
00472F26 . 68 68604F00 PUSH ORE_TUBA.004F6068 ; /Arg2 = 004F6068
00472F2B . 52 PUSH EDX ; |Arg1
00472F2C . C745 CC 05000>MOV DWORD PTR SS:[EBP-34],5 ; |
00472F33 . E8 5A8D0600 CALL ORE_TUBA.004DBC92 ; \ORE_TUBA.004DBC92
00472F38 > 8B35 B0D14E00 MOV ESI,DWORD PTR DS:[<&KERNEL32.CreateF>; kernel32.CreateFileA
00472F3E . 6A 00 PUSH 0 ; /hTemplateFile = NULL
00472F40 . 68 80000000 PUSH 80 ; |Attributes = NORMAL
00472F45 . 6A 03 PUSH 3 ; |Mode = OPEN_EXISTING
00472F47 . 6A 00 PUSH 0 ; |pSecurity = NULL
00472F49 . 6A 01 PUSH 1 ; |ShareMode = FILE_SHARE_READ
00472F4B . 8D85 B4FEFFFF LEA EAX,DWORD PTR SS:[EBP-14C] ; |
00472F51 . 68 00000080 PUSH 80000000 ; |Access = GENERIC_READ
00472F56 . 50 PUSH EAX ; |FileName="I:\Program Files\Navel\壌偨偪偵梼偼側偄\SCRIPT\LL_APP.SYS"
00472F57 . FFD6 CALL ESI ; \CreateFileA
00472F59 . 8BF8 MOV EDI,EAX
00472F5B . 83FF FF CMP EDI,-1
00472F5E . 897D E8 MOV DWORD PTR SS:[EBP-18],EDI
00472F61 . 74 7E JE SHORT ORE_TUBA.00472FE1 ;跳转
这个打开一个不存在的文件,但看文件名应该猜到,这个就是解密后的脚本文件了。但由于现在文件还不存在,所以会跳到后面的代码继续执行,一开始还是一些字符串处理的流程,然后到了0x0047310d的一个call,跟进去看看,一开始是一个嵌套的循环,处理字符串的.循环后出来到了下面一个call
00486840 . 81EC 04010000 SUB ESP,104 ; 抬高堆栈
00486846 . 56 PUSH ESI
00486847 . 8BF1 MOV ESI,ECX
00486849 . 8A46 0D MOV AL,BYTE PTR DS:[ESI+D]
0048684C . 84C0 TEST AL,AL
0048684E . 74 6C JE SHORT ORE_TUBA.004868BC
00486850 . 8B8424 0C0100>MOV EAX,DWORD PTR SS:[ESP+10C]
00486857 . 8D4C24 04 LEA ECX,DWORD PTR SS:[ESP+4]
0048685B . 50 PUSH EAX ; /String2
0048685C . 51 PUSH ECX ; |String1
0048685D . FF15 00D14E00 CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>] ; \lstrcpyA
00486863 . 8D5424 04 LEA EDX,DWORD PTR SS:[ESP+4]
00486867 . 52 PUSH EDX ; /StringOrChar
00486868 . FF15 54D34E00 CALL DWORD PTR DS:[<&USER32.CharLowerA>] ; \CharLowerA
0048686E . 8A4E 24 MOV CL,BYTE PTR DS:[ESI+24]
00486871 . 8B56 20 MOV EDX,DWORD PTR DS:[ESI+20] ; [ESI+20]中的数据是0x00AE4012,而前面从文件中读取0x3f03字节的内容的缓冲区是0x00AE4008
00486874 . 8D4424 04 LEA EAX,DWORD PTR SS:[ESP+4] ; 取“ll_app.sys”地址
00486878 . 6A 00 PUSH 0
0048687A . 50 PUSH EAX
0048687B . 51 PUSH ECX
0048687C . 52 PUSH EDX
0048687D . E8 DEC30400 CALL ORE_TUBA.004D2C60 ; 又是一个处理字符串的call
然后一直单步到下面这个call:
00486898 . 51 PUSH ECX ; /Arg5
00486899 . 8B8C24 180100>MOV ECX,DWORD PTR SS:[ESP+118] ; |
004868A0 . 52 PUSH EDX ; |Arg4
004868A1 . 8B9424 180100>MOV EDX,DWORD PTR SS:[ESP+118] ; |
004868A8 . 51 PUSH ECX ; |Arg3
004868A9 . 52 PUSH EDX ; |Arg2
004868AA . 50 PUSH EAX ; |Arg1
004868AB . 8BCE MOV ECX,ESI ; |
004868AD . E8 5E000000 CALL ORE_TUBA.00486910 ;
\重要,跟进去看看,发现又是一个嵌套循环,循环完了后又有一个call,跟进去后又是一个
call……再进去后,发现到达目标了
00486780 /$ 8B41 38 MOV EAX,DWORD PTR DS:[ECX+38]
00486783 |. 56 PUSH ESI
00486784 |. 85C0 TEST EAX,EAX
00486786 |. 57 PUSH EDI
00486787 |. 74 26 JE SHORT ORE_TUBA.004867AF
00486789 |. 8B5424 0C MOV EDX,DWORD PTR SS:[ESP+C]
0048678D |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10]
00486791 |. 8B7C24 14 MOV EDI,DWORD PTR SS:[ESP+14]
00486795 |. 8B44D0 04 MOV EAX,DWORD PTR DS:[EAX+EDX*8+4]
00486799 |. 8A50 1D MOV DL,BYTE PTR DS:[EAX+1D]
0048679C |. 8816 MOV BYTE PTR DS:[ESI],DL
0048679E |. 8B50 04 MOV EDX,DWORD PTR DS:[EAX+4]
004867A1 |. 8B70 14 MOV ESI,DWORD PTR DS:[EAX+14]
004867A4 |. 8917 MOV DWORD PTR DS:[EDI],EDX
004867A6 |. 8B40 08 MOV EAX,DWORD PTR DS:[EAX+8]
004867A9 |. 8B5424 18 MOV EDX,DWORD PTR SS:[ESP+18]
004867AD |. EB 5F JMP SHORT ORE_TUBA.0048680E
004867AF |> 8A51 0F MOV DL,BYTE PTR DS:[ECX+F]
004867B2 |. 33C0 XOR EAX,EAX
004867B4 |. 84D2 TEST DL,DL
004867B6 |. 0F95C0 SETNE AL
004867B9 |. 83C0 02 ADD EAX,2
004867BC |. 8B71 2C MOV ESI,DWORD PTR DS:[ECX+2C] ; esi=缓冲区内文件头的偏移起始地址
004867BF |. 8D0485 010000>LEA EAX,DWORD PTR DS:[EAX*4+1]
004867C6 |. 0FAF4424 0C IMUL EAX,DWORD PTR SS:[ESP+C] ; eax=文件头大小*文件的号数
004867CB |. 8A1430 MOV DL,BYTE PTR DS:[EAX+ESI]
004867CE |. 03C6 ADD EAX,ESI
004867D0 |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10]
004867D4 |. 40 INC EAX
004867D5 |. 83C0 04 ADD EAX,4
004867D8 |. 8816 MOV BYTE PTR DS:[ESI],DL
004867DA |. 8B70 FC MOV ESI,DWORD PTR DS:[EAX-4] ; 目标文件数据在SCRIPT.LPK内的偏移,esi=0x15EDF
004867DD |. 8A51 0C MOV DL,BYTE PTR DS:[ECX+C]
004867E0 |. 84D2 TEST DL,DL
004867E2 |. 74 03 JE SHORT ORE_TUBA.004867E7
004867E4 |. C1E6 0B SHL ESI,0B
004867E7 |> 8B7C24 14 MOV EDI,DWORD PTR SS:[ESP+14]
004867EB |. 8B10 MOV EDX,DWORD PTR DS:[EAX] ; 目标文件的大小,edx=0x19
004867ED |. 53 PUSH EBX
004867EE |. 8917 MOV DWORD PTR DS:[EDI],EDX
004867F0 |. 8B5424 1C MOV EDX,DWORD PTR SS:[ESP+1C]
004867F4 |. C702 00000000 MOV DWORD PTR DS:[EDX],0
004867FA |. 8A59 0F MOV BL,BYTE PTR DS:[ECX+F]
从这一串代码中可以看出很多有用的信息,配合数据窗口中的数据,就能写出文件头的结构了:
struct fileheader{
BYTE type; //?
DWORD offset;
DWORD size;
DWORD unknow;
};
我怎么能确定上面的结构是否正确?因为紧跟着这段代码的是如下的调用:
00486813 |. 6A 00 PUSH 0 ; /Origin = FILE_BEGIN
00486815 |. 6A 00 PUSH 0 ; |pOffsetHi = NULL
00486817 |. 56 PUSH ESI ; |OffsetLow=15EDF
00486818 |. 51 PUSH ECX ; |hFile
00486819 |. FF15 50D24E00 CALL DWORD PTR DS:[<&KERNEL32.SetFilePoi>; \SetFilePointer
这个函数这样就完了。继续
0048694E |. E8 2DFEFFFF CALL ORE_TUBA.00486780 ; 这个上面的函数
00486953 |. 85C0 TEST EAX,EAX
00486955 |. 74 0E JE SHORT ORE_TUBA.00486965
00486957 |. 5F POP EDI
00486958 |. 5E POP ESI
00486959 |. B8 06000000 MOV EAX,6
0048695E |. 5B POP EBX
0048695F |. 8BE5 MOV ESP,EBP
00486961 |. 5D POP EBP
00486962 |. C2 1400 RETN 14
00486965 |> 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
00486968 |. 85C0 TEST EAX,EAX
0048696A |. 75 0E JNZ SHORT ORE_TUBA.0048697A
0048696C |. 5F POP EDI
0048696D |. 5E POP ESI
0048696E |. B8 02000000 MOV EAX,2
00486973 |. 5B POP EBX
00486974 |. 8BE5 MOV ESP,EBP
00486976 |. 5D POP EBP
00486977 |. C2 1400 RETN 14
0048697A |> 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+10]
0048697D |. 8B55 F4 MOV EDX,DWORD PTR SS:[EBP-C]
00486980 |. 6A 00 PUSH 0
00486982 |. 51 PUSH ECX
00486983 |. 52 PUSH EDX
00486984 |. FFD6 CALL ESI ; 这个是分配缓冲区,edx就是刚才的0x19,这样就可以真正确定文件头的结构了
再经过一大串跳转,终于开始读数据了:
00486A51 |. 6A 00 PUSH 0 ; /pOverlapped = NULL
00486A53 |. 50 PUSH EAX ; |pBytesRead
00486A54 |. 56 PUSH ESI ; |BytesToRead=0x19
00486A55 |. 51 PUSH ECX ; |Buffer=0015C198
00486A56 |. 8B4F 08 MOV ECX,DWORD PTR DS:[EDI+8] ; |
00486A59 |. 51 PUSH ECX ; |hFile
00486A5A |. FF15 3CD24E00 CALL DWORD PTR DS:[<&KERNEL32.ReadFile>] ; \ReadFile
读取文件成功后,下面终于到达我们的最终目标——解密过程了。解密过程分两段,第一段如下所示:
00486A78 |> \8B4D FC MOV ECX,DWORD PTR SS:[EBP-4] ; 上面读入数据缓冲区地址
00486A7B |> 8A45 0F MOV AL,BYTE PTR SS:[EBP+F]
00486A7E |. 84C0 TEST AL,AL
00486A80 |. 74 27 JE SHORT ORE_TUBA.00486AA9
00486A82 |. 85F6 TEST ESI,ESI ; esi为读入数据的大小
00486A84 |. 8975 08 MOV DWORD PTR SS:[EBP+8],ESI
00486A87 |. 74 20 JE SHORT ORE_TUBA.00486AA9
00486A89 |> 8A01 /MOV AL,BYTE PTR DS:[ECX]
00486A8B |. 8AD0 |MOV DL,AL
00486A8D |. 34 50 |XOR AL,50
00486A8F |. 80F2 FD |XOR DL,0FD
00486A92 |. 80E2 0F |AND DL,0F
00486A95 |. C0E2 04 |SHL DL,4
00486A98 |. C0E8 04 |SHR AL,4
00486A9B |. 02D0 |ADD DL,AL
00486A9D |. 8811 |MOV BYTE PTR DS:[ECX],DL
00486A9F |. 8B45 08 |MOV EAX,DWORD PTR SS:[EBP+8]
00486AA2 |. 41 |INC ECX
00486AA3 |. 48 |DEC EAX
00486AA4 |. 8945 08 |MOV DWORD PTR SS:[EBP+8],EAX
00486AA7 |.^ 75 E0 \JNZ SHORT ORE_TUBA.00486A89
转成c代码如下:
unsigned char data[];//缓冲区
unsigned char a,b;
for(int i=0;i<0x19;i++)
{
a=data[i];
b=a;
a^=0x50;
b^=0xfd;
b&=0x0f;
b<<=4;
a>>=4;
b+=a;
data[i]=b;
}
第二段解密过程为:
00486AC6 |> \8B47 14 MOV EAX,DWORD PTR DS:[EDI+14] ; 前面所算出来的第一个密钥,0xA8E7FAF1
00486AC9 |. C745 F0 85627>MOV DWORD PTR SS:[EBP-10],31746285 ; 立即数解密密钥
00486AD0 |. 8945 EC MOV DWORD PTR SS:[EBP-14],EAX
00486AD3 |. 8B45 EC MOV EAX,DWORD PTR SS:[EBP-14] ; eax=0xA8E7FAF1
00486AD6 |. 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10] ; ecx=0x31746285
00486AD9 |. 8B75 FC MOV ESI,DWORD PTR SS:[EBP-4] ; esi=0x0015C198,缓冲区地址
00486ADC |. 8B5D 08 MOV EBX,DWORD PTR SS:[EBP+8] ; ebx=0x06,即为缓冲区大小,此时每次循环解密一个双字
00486ADF |> C1C9 04 /ROR ECX,4
00486AE2 |. 3106 |XOR DWORD PTR DS:[ESI],EAX
00486AE4 |. D3C0 |ROL EAX,CL
00486AE6 |. 83C6 04 |ADD ESI,4
00486AE9 |. 4B |DEC EBX
00486AEA |.^ 75 F3 \JNZ SHORT ORE_TUBA.00486ADF ; 跳回去继续循环
这段代码跟前面的是不是很像?不过有些不一样,首先是解密的密钥用的是第一个不是用第二个,然后是循环移位的方向与前面的相反了,写成c代码如下:
int data[]//缓冲区地址
int n=0x31746285;
int key=0xa8e7faf1;;
for(i=0;i<0x19/4;i++)
{
n=ror(n,4);
data[i]^=key;
key=rol(key,*(char*)&n);
}
好了,整个解密算法就完了。怎样知道答案对不对?用专门的解包软件(如crass)将脚本文件解密,用WinHex打开对应的文件然后对比一下就知道了。
另外,上述的解密过程一次解密一个双字的,所以最后剩下一个字节没有做任何操作。
剩下最后一个问题是,前面所说脚本文件的个数是0x1a2,这个怎样确定?由于有些脚本文件在游戏过程中才进行解密的,所以我用一个很笨的方法来确定:用软件解密脚本文件后,脚本文件总数418,即16进制的0x1a2.
限于篇幅,其他资源的解密过程就不贴了,其他资源的解密要用到解密后的脚本文件中的数据做key,至于解密后各个脚本的文件名么……没分析出来orz。