• 标 题:皮兄,网际金典3的InstallShield序列号破解。 (9千字)
  • 作 者:Passion
  • 时 间:2001-4-28 23:27:06
  • 链 接:http://bbs.pediy.com

网际金典3的InstallShield序列号破解。

受皮特陈兄指使,也不知从哪儿弄来个网际金典3的不完全版,非得说要咱按“典型”的InstallShield破解法来找个序列号不可。临危受命,不可不从,谁叫这位FCG的老大让咱“难望其项背”呢?好,小猫下了半夜,睡一觉后起床穿衣洗脸刷牙吃饭后……开始!

网际金典安装需要序列号,简单的一小段,似乎是InstallShield安装的典型。于是先用ISDCC2反编译setup.ins到一个文件中,打开这个文件,找开始的函数声明部分(为什么不找出错信息呢?原因……嘿嘿,声明部分在前面嘛,容易看见。),果然有下面的几行:

……
    prototype InstDll.RWCheckSerialNo(LIST, LIST, LIST);
    prototype InstDll.InstallTcFont(LIST);
……


这说明了什么?越来越多的软件不把序列号的检验过程放在InstallShield的脚本文件中,大概是免得反编译一下就看出算法来了,因此多多少少也找些DLL来把过程隐藏在里面。这里就说明SETUP程序需要用到一个叫InstDll.dll文件中的RWCheckSerialNo过程。
下一步,不用找什么出错信息了,直接在文件中查找字串“RWCheckSerialNo”,一下就找到了,下面是相关的上下文内容:

    // ------------- FUNCTION function95 --------------
    function function95(pString0, pString1, pString2)
        number lNumber0;
        number lNumber1;
        number lNumber2;
        number lNumber3;
        number lNumber4;
        string lString0;
    begin

label812:
00F164:00BA:        AddressString(pString0);
00F169:0021:        lNumber0 = LAST_RESULT;
00F171:00BA:        AddressString(pString1);
00F176:0021:        lNumber1 = LAST_RESULT;
00F17E:00BA:        AddressString(pString2);
00F183:0021:        lNumber2 = LAST_RESULT;
00F18B:0125:        lString0 = SUPPORTDIR ^ "INSTDLL.DLL";
00F1A1:00B2:        UseDLL(lString0);

//Usedll,说明INSTDLL.DLL是通过loadlibrarya动态加载的。

00F1A6:00B4:        InstDll.RWCheckSerialNo(lNumber0, lNumber1, lNumber2);
00F1B4:0021:        lNumber3 = LAST_RESULT;
00F1BC:00B3:        UnUseDLL(lString0);

//卸载INSTDLL.DLL。

00F1C1:0128:        lNumber4 = lNumber3 <= 0;    

//这句话应该说明一下。其实应该是lNumber4 = (lNumber3 <= 0),也就是说如果lNumber3小于等于0,则这个逻辑式为真,lNumber4的值就是1,下面的这个IF就会直接返回,不会跳转到正确的label814。——这里也告诉我们,RWCheckSerialNo要返回大于0的值才表示序列号通过检验,这点对于等会儿分析INSTDLL.DLL的代码是很重要的。

00F1D3:0022:        if (lNumber4 = 0) then
                        goto label814;
                    endif;
00F1E1:012F:        return(0);
00F1EE:012F:        return(1);
00F1F7:00B8:        return;
    end;

调用这个FUNCTION95的程序段也可以找到:

label10:
00112E:00B5:        function115();
001136:0021:        lNumber0 = LAST_RESULT;
00113E:0128:        lNumber4 = lNumber0 = 12;
001150:0022:        if (lNumber4 = 0) then
                        goto label12;
                    endif;
00115E:002C:        goto label10;

label11:
001167:00B5:        function116();
00116F:0021:        lNumber0 = LAST_RESULT;
001177:0128:        lNumber4 = lNumber0 = 12;
001189:0022:        if (lNumber4 = 0) then
                        goto label13;
                    endif;
001197:002C:        goto label11;
00119C:002C:        goto label14;

label12:
0011A5:00B5:        function95(string11, string6, string7);      //检验序列号
0011B6:0021:        lNumber4 = LAST_RESULT;
0011BE:0128:        lNumber4 = lNumber4 = 0;
0011D0:0022:        if (lNumber4 = 0) then              //错则显示提示
                        goto label14;
                    endif;
0011DE:0112:        StrLoadString("", "MSG_INFO", lString1);
0011F1:003B:        SetDialogTitle(4, lString1);
0011FB:0112:        StrLoadString("", "ERROR_CHECK_SERIAL_NO", lString1);
00121B:002A:        MessageBox(lString1, -65535);
001225:002C:        goto label12;

好了,setup.ins里已经没什么地方可以做手脚了,运行setup.exe,在Windows\temp\_istmp0.dir下能找到一个instdll.dll文件,哈哈,才23k,考虑到编译时的无用代码,核心检验函数无论怎样都不会太复杂。不管怎样,先复制出来反汇编一下吧!
instdll.dll输出函数RWCheckSerialNo的地址是1000(参看以下代码),这得先记住。
功力高的话可以直接后分析反汇编后的代码,如果像我这样不怎么样的“低”手,就只有动态跟踪了。SETUP本身的框架比较复杂,hmemcpy这种断点只会把局面弄得更复杂。前面不是说了instdll.dll是动态加载的吗?于是运行SETUP.EXE后在序列号输入界面处切入SOFTICE,下断bpx loadlibrarya,回来按下一步马上断掉。
按几次F12返回到高层,用MOD INSTDLL看看刚加载的INSTDLL.DLL地址,我这里是01A50000,加上RWCheckSerialNo函数的偏移1000,u 1A51000便是RWCheckSerialNo的入口,下断bpx 0167:01a51000。
下面就是序列号的分析过程了:

Exported fn(): RWCheckSerialNo - Ord:0004h
:10001000 83EC10                  sub esp, 00000010
:10001003 56                      push esi
:10001004 57                      push edi
:10001005 8B7C241C                mov edi, dword ptr [esp+1C]    //输入的序列号地址。
:10001009 33F6                    xor esi, esi
:1000100B 57                      push edi
:1000100C E8FF000000              call 10001110            //跟进去。
:10001011 83C404                  add esp, 00000004
:10001014 85C0                    test eax, eax            //如果懒得跟,改这里即可。
:10001016 740D                    je 10001025
:10001018 B801000000              mov eax, 00000001        //正确标志。
:1000101D 5F                      pop edi
:1000101E 5E                      pop esi
:1000101F 83C410                  add esp, 00000010
:10001022 C20C00                  ret 000C

下面是10001110处的内容:

* Referenced by a CALL at Addresses:
|:1000100C  , :100011AC 
|
:10001110 83EC50                  sub esp, 00000050
:10001113 56                      push esi
:10001114 8B742458                mov esi, dword ptr [esp+58]
:10001118 56                      push esi

* Reference To: KERNEL32.lstrlenA, Ord:029Ch
                                  |
:10001119 FF15AC810010            Call dword ptr [100081AC]
:1000111F 83F80D                  cmp eax, 0000000D
:10001122 7407                    je 1000112B

//检测输入的序列号长度必须为0x0D个字符,也就是13个。

:10001124 33C0                    xor eax, eax
:10001126 5E                      pop esi
:10001127 83C450                  add esp, 00000050
:1000112A C3                      ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10001122(C)
|
:1000112B 8D442404                lea eax, dword ptr [esp+04]
:1000112F 56                      push esi
:10001130 50                      push eax

* Reference To: KERNEL32.lstrcpyA, Ord:0296h
                                  |
:10001131 FF1548810010            Call dword ptr [10008148]

//复制一下以备分析,此时ESP+4指向输入的序列号。

:10001137 0FBE542404              movsx edx, byte ptr [esp+04]
:1000113C 0FBE4C2407              movsx ecx, byte ptr [esp+07]
:10001141 8BC2                    mov eax, edx
:10001143 2BC1                    sub eax, ecx
:10001145 83F8FF                  cmp eax, FFFFFFFF
:10001148 7407                    je 10001151

//这段说明序列号的第四个字符必须比第一个字符大1(Ascii码)。

:1000114A 33C0                    xor eax, eax
:1000114C 5E                      pop esi
:1000114D 83C450                  add esp, 00000050
:10001150 C3                      ret


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10001148(C)
|
:10001151 0FBE442408              movsx eax, byte ptr [esp+08]
:10001156 2BD0                    sub edx, eax
:10001158 83FA04                  cmp edx, 00000004
:1000115B 7407                    je 10001164

//这段说明序列号的第一个字符必须比第五个字符大4(Ascii码)。

:1000115D 33C0                    xor eax, eax
:1000115F 5E                      pop esi
:10001160 83C450                  add esp, 00000050
:10001163 C3                      ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:1000115B(C)
|
:10001164 0FBE442405              movsx eax, byte ptr [esp+05]
:10001169 0FBE4C2406              movsx ecx, byte ptr [esp+06]
:1000116E 2BC1                    sub eax, ecx
:10001170 83F8F8                  cmp eax, FFFFFFF8
:10001173 7407                    je 1000117C


//这段说明序列号的第三个字符必须比第二个字符大8(Ascii码)。

:10001175 33C0                    xor eax, eax
:10001177 5E                      pop esi
:10001178 83C450                  add esp, 00000050
:1000117B C3                      ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10001173(C)
|
:1000117C 8D442409                lea eax, dword ptr [esp+09]

* Possible StringData Ref from Data Obj ->"lqhyhtlj"
                                  |
:10001180 6850500010              push 10005050
:10001185 50                      push eax

* Reference To: KERNEL32.lstrcmpiA, Ord:0293h
                                  |
:10001186 FF154C810010            Call dword ptr [1000814C]

//然后从第六个字符开始的八个字符必须是“lqhyhtlj”

:1000118C 5E                      pop esi
:1000118D 83F801                  cmp eax, 00000001
:10001190 1BC0                    sbb eax, eax
:10001192 83C450                  add esp, 00000050
:10001195 F7D8                    neg eax
:10001197 C3                      ret

于是我们可以捏造一个我们可以用的序列号,我捏的是“81994lqhyhtlj”,为保险一点又捏造了一个“e19falqhyhtlj”,安装时填入,果然都通过了(出现了下一步的自定义安装路径的对话框)。
还有一点,instdll.dll在你输入的字串长度为14时还有另外一串更复杂的检验算法,我下的不是完全安装版,没法子判断到底哪个才是正宗的,或者两个都是?——这就得靠皮特陈兄用正式安装版来检验检验了。^_^