前言:游戏汉化中的一个重要的步骤是对资源的解包以及封包。虽然现在有很多软件可以实现解包的功能,但要将资源修改后再封包的话,不熟悉解密的算法是无法写出逆算法的。脚本是游戏中的一个最重要的资源,负责控制游戏引擎的行为以及存储了游戏的文本。今天我就以某个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);
}
但从文件中读取的后四个字节还不知道有什么用,先不管。下面继续单步跟过去:
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.

限于篇幅,其他资源的解密过程就不贴了,原理都是一样的。

后记:记得出接触破解是在去年2月份,但一直以来都是小打小闹,遇到要跟踪算法的一律不放弃。后来接触了galgame,听到了痴汉工贼,dwing以及色神等技术大牛,于是就下定决心来逆一个游戏的算法。第一次写破文,写得不好或者有错的地方请见谅。
另外,向全体奋战在汉化第一线的同志致敬!