《俺たちに翼はない》脚本加密算法分析

本文详细解析了一款游戏中的脚本文件解密过程,包括关键的解密算法及其实现,通过跟踪游戏内部调用流程揭示了密钥生成与数据解密的原理。
部署运行你感兴趣的模型镜像
前言:游戏汉化中的一个重要的步骤是对资源的解包以及封包。虽然现在有很多软件可以实现解包的功能,但要将资源修改后再封包的话,不熟悉解密的算法是无法写出逆算法的。脚本是游戏中的一个最重要的资源,负责控制游戏引擎的行为以及存储了游戏的文本。

首先是老规矩,对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代码如下:
[code]#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);
}[/code]
但从文件中读取的后四个字节还不知道有什么用,先不管。下面继续单步跟过去:

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代码如下:[code]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);
}[/code]
后面还有一段读取缓冲区的操作:

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
可以看出第一个双字是疑似脚本文件个数,中间两个字节作用未知,然后一个双字是一个偏移,用结构可以表达如下:
[code]struct header{
DWORD numberoffile;
BYTE unknow[2];
DWORD offset;
……};[/code]
这个函数剩下的地方都没什么猛料,回到原来的函数后继续跟发现都是一些字符串的处理,一直跟到下面地址为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]

从这一串代码中可以看出很多有用的信息,配合数据窗口中的数据,就能写出文件头的结构了:
[code]struct fileheader{
BYTE type; //?
DWORD offset;
DWORD size;
DWORD unknow;
};[/code]
我怎么能确定上面的结构是否正确?因为紧跟着这段代码的是如下的调用:

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代码如下:
[code]
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;
}[/code]
  第二段解密过程为:

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代码如下:
[code]
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);
}[/code]
  好了,整个解密算法就完了。怎样知道答案对不对?用专门的解包软件(如crass)将脚本文件解密,用WinHex打开对应的文件然后对比一下就知道了。
另外,上述的解密过程一次解密一个双字的,所以最后剩下一个字节没有做任何操作。
剩下最后一个问题是,前面所说脚本文件的个数是0x1a2,这个怎样确定?由于有些脚本文件在游戏过程中才进行解密的,所以我用一个很笨的方法来确定:用软件解密脚本文件后,脚本文件总数418,即16进制的0x1a2.

  限于篇幅,其他资源的解密过程就不贴了,其他资源的解密要用到解密后的脚本文件中的数据做key,至于解密后各个脚本的文件名么……没分析出来orz。

您可能感兴趣的与本文相关的镜像

Anything-LLM

Anything-LLM

AI应用

AnythingLLM是一个全栈应用程序,可以使用商用或开源的LLM/嵌入器/语义向量数据库模型,帮助用户在本地或云端搭建个性化的聊天机器人系统,且无需复杂设置

GAL游戏解包工具,含C++源码。 包含181个工具: alb2png amp2bmp ar2bugfix arkcmp2bmp brs2png d3dslide decrbrads decrddns2 decrkansa erisacvt ex1uparc ex4ag exafa exah3pac exakbdat exald exanepak exaos exaqugsp exar2 exarc2 exarc4 exarcw exatuworks exavk exbbbin exbelarc exbelldat_nosrc exbelldat1.02_nosrc exbkarc exbrdat_nosrc exbrpak exbsa exbszdat excandydat excatcab excdt excfarc exchpac excrxb exdaf2 exddp exdebopak exdieslib exdosnpac exdpk exdpmx exdxa exedendp5 exeiarc exescarc exfavbin exfigdat exfleurdat exfp3 exgce3 exgr2 exgsp4 exgxp exhappyend exhdcpak exhecdat exhud exhxp exiar exifdypf exiga exihkcap exisa exk5 exkactive exkhwag exkifint exkiss6dat exkizpak exkkesui exkleinpak exkoigdat exl4osa_nosrc exl5enj_nosrc exl6ren_nosrc exlac exlfa exlfar21 exlibiarc exlnk4 exlrlpk_nosrc exm2lib exmaiarc exmaotarc exmarupac_nosrc exmaspaz exmed exmespac exmhpac exmk2 exmnvdat exmoepack exmornpak exmpf2 exmpk exmpsaf exmugibin exmwpak exnfcel exnllpk_nosrc exnnkarc exnosdat_nosrc exnp4x exnpa exnpf exns2 exoozoarc expatbin expcf expdfpk expf2 expimg expkd expzdat exrlarc_nosrc exrrdat_nosrc exs4alf exscharc_nosrc exsec exsenarc_nosrc exsgnpa exshikidat exsholib exsteldat exsui2rom exszs extafarc extapak extarc extcd3 extensho extk2fpk extmmpck extricgf_nosrc extropak extskfdat exttd extttdpk extvkarc_nosrc exuni2 exunity exutsudat exvcpak exvff exvfs exwatfopck exwhaledat exwlcs exxusegd exyatpkg exykc exyox exzwarc_nosrc gax2png gyu2bmp junk_atled junk_monobeno kamiparadeck lpx2bmp mag2bmp miscbss misceri miscmja nimg2bmp pmchar2bmp ps2force480p spc2bmp sz2png tig2png tits2deck tox2deck toxtweak tpw2bmp_nosrc VisualMemory0.9.5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值