http://www.centra.com/index_flash.asp - Webpage.
This paper discusses interpretation of decompiled InstallShield scripts. If you have the patience it is possible to achieve the same result using SoftICE. The Centra installer is much like many others, before proceeding you are requested to insert a License Key, most InstallShields can be divided into 2 types of protection strategy, those that use the script (what I'm discussing here) and those that use their own external dll (who can forget the infamous AdobeFunc1() ). Versions of scripts differ also in their storage of strings, older versions of InstallShield left them in the script itself, later versions (probably for modularity) use C style defines.
The data strings in Centra are of the new type, when installing an InstallShield application be sure to check very closely the temporary directory as this should give you a very good idea of the location of the protection. Be sure to snaffle also any error messages. Centra's License Key screen has 7 boxes (each can take 5 characters) except the 2nd box which takes only 2. I filled all the boxes initially with 0 and got an 'invalid checksum' error. Correlating this to the defines in value.shl we find that our error is defined as 'STR_INVALID_LICENSE_KEY_CHECKSUM'.
Using IsDcc we decompile setup.ins. We quickly locate the deciding code :-
00383E:0022: if (lNumber13 = 0) then goto label81; // key passed checksum endif; 00384C:0112: StrLoadString("", "STR_INVALID_LICENSE_KEY_CHECKSUM", lString16);
Simply in English, 'if lNumber13 = 0' then our key passed the checksum, if not display the string. Let us now scroll up and analyse how we can generate a valid license key.
00366D:0030: StrSub(lString6, string18, 0, 5); 00367F:0030: StrSub(lString7, string18, 6, 2); 003691:0030: StrSub(lString13, string18, 9, 2); // note, not used in strcat 0036A3:0030: StrSub(lString8, string18, 11, 3); 0036B5:0030: StrSub(lString9, string18, 15, 5); 0036C7:0030: StrSub(lString10, string18, 21, 5); 0036D9:0030: StrSub(lString11, string18, 27, 5); 0036EB:0030: StrSub(lString12, string18, 33, 5); // break down the key 0036FD:0124: lString16 = lString6 + lString7; 003708:0124: lString16 = lString16 + lString8; 003713:0124: lString16 = lString16 + lString9; 00371E:0124: lString16 = lString16 + lString10; 003729:0124: lString16 = lString16 + lString11; 003734:0124: lString14 = lString16 + lString12; // concatenate together 00373F:0021: lNumber8 = 0; 003749:0021: lNumber9 = 0; // set some locals to 0 003753:002F: StrLength(lString14); 003758:0021: lNumber13 = LAST_RESULT; // lNumber13 = strlen
The first 8 lines perform string operations, the format for StrSub appears to be :-
StrSub(String dest, String source, start index, length);
So here we see various String assignments stripping away individual parts of the license key. The next 6 lines concatenate the key together, notice here that lString13 is not used. At the end two variables are set to 0 and lNumber13 holds the value of the string length of our concatenated string. The reason for clearing the 2 variables becomes clear with the next part of the script :-
label79: 003764:0128: lNumber14 = lNumber9 <= lNumber13; 003774:0022: if (lNumber14 = 0) then goto label80; endif; 003782:007A: GetByte(lNumber14, lString14, lNumber9); 00378D:0119: lNumber8 = lNumber8 + lNumber14; // store 003798:0119: lNumber9 = lNumber9 + 1; // loop counter 0037A5:002C: goto label79;
This code is effectively a for loop but could just as easily be thought of as a do / while. lNumber9 acts as the loop counter, lNumber8 as the storage area and lNumber14 the current byte, when we reach a null the loop terminates. So we deduce that the code here merely sums up all of the bytes in lString14 and stores the result in lNumber8, the prototype for GetByte() appears to be :-
GetByte(char dest, String source, int index); // we treat the source String as an array of chars
Lets see what happens next :-
label80: 0037AE:0123: lNumber8 = lNumber8 % 0x400; // mod sum with 0x400 0037BB:011D: lNumber10 = lNumber8 & 31; // & with 0x1F 0037C8:0122: lNumber13 = lNumber8 >> 5; // shr 5 0037D5:011D: lNumber11 = lNumber13 & 31; // & with 0x1F 0037E2:0013: lString14 = " "; // 2 space characters
So the sum of our string is mod'd with 0x400 and stored in lNumber8. lNumber10 &'s this with 31 decimal (0x1F) and saves it in lNumber10. lNumber13 shifts lNumber8 right by 5 (i.e. divides by 32 decimal) and &'s this result with 31 decimal, saving it in lNumber11. So far so good (2 results, lNumber10 & lNumber11) :-
0037EC:007A: GetByte(lNumber13, lString15, lNumber10); 0037F7:007B: SetByte(lString14, 0, lNumber13); 003804:007A: GetByte(lNumber13, lString15, lNumber11); 00380F:007B: SetByte(lString14, 1, lNumber13); 00381C:0023: StrCompare(lString14, lString13); 003824:0021: lNumber13 = LAST_RESULT;
Remembering the prototype for GetByte(), we see that our results are used to index into another String (lString15), this is actually defined at the start of the function block as so :-
lString15 = "e4a9m7cfnpgiqb3r6hstdukvj82wx5yz";
The SetByte()'s place the byte retrieved from lString15 in lString14 which we saw defined previously as 2 spaces. Finally lNumber13 holds the string compare result of lString14 and lString13, lString13 being the 2 bytes from our License Key which weren't concatenated to the string fed through the loop. To pass this check its probably easiest to write a small program which works out the required table entries. Finally :-
label82: 00389E:0021: number54 = 0; 0038A8:011A: lNumber13 = lNumber1 - 1; // lNumber1 = strlen-1. 0038B5:0030: StrSub(lString4, lString1, lNumber13, 1); // get the end char 0038C5:0023: StrCompare(lString4, "a"); // compare it with "a"
At this code we get the last byte of the License Key by taking 1 away from the string length, it is compared to lowercase and uppercase a / b / c / d & e (I found out that the uppercase checks don't work as the program seems to lower case everything by default). Depending on this letter you get to install a different flavour of Centra (in fact there appears to be some duplicity of 3 core options). In conclusion then we can see that the License Key protection for Centra is pitifully easy, one needs only to decide on the end byte (the flavour) and write a quick for loop in whatever language (C is probably the easiest) to generate the missing value in the License Key.
As an aside, most of these operations, concatenation, StrLen() and StrCompare() for example, are implemented as pure calls to well known kernel32 functions e.g. lstrcat(), lstrcmpi() & lstrlen(). In this instance working methodically through the script proved to be considerably more effective than SoftICE.
You may care to verify the following codes :-
Centra Enterprise Server 11111-22-rh888-44444-55521-11111-7514b
Centra Server 11111-22-rh888-44444-55521-11111-7512d
Centra Collaboration Server 11111-22-rh888-44444-55521-11111-7511e