Arash solution for Lord Caligo's RBSTRIAL.COM
By
ARASH


Here is a complete well explained solution for Lord Caligo's RBSTRIAL.COM. (found at lord Caligo's site). It's is always a good execise to crack "Try me's" written by other crackers to see new shapes of protections. Arash guides you trough the encryption functions used in this program and explains step by step what's giong on. Source code for a password generator in pascal (assembly prefered) is also included. Read and enjoy. Lets hope for more contributions from Arash

HOW TO CRACK RBSTRIAL.COM

A word of caution before we begin: this method is likely not the most efficient as it's the first program I ever cracked. The good thing is that it works though.

Alright, pick up a copy of RBSTRIAL.COM from Lord Caligo's site and let's start. Run the program a couple of time to see the sequence of actions. Since the file is very small (308 bytes), you might as well print the entire disassembled code and have a look. Here's a disassembly:


241E:0100 6B4F5547      IMUL    CX,[BX+55],+47
241E:0104 45            INC     BP
241E:0105 52            PUSH    DX
241E:0106 21D2          AND     DX,DX
241E:0108 4D            DEC     BP
241E:0109 5A            POP     DX
241E:010A E98A00        JMP     0197
...
241E:0190 F6D8          NEG     AL
241E:0192 FEC3          INC     BL
241E:0194 EB3F          JMP     01D5
241E:0196 90            NOP
241E:0197 B409          MOV     AH,09
241E:0199 BA0D01        MOV     DX,010D
241E:019C CD21          INT     21
241E:019E BA8001        MOV     DX,0180
241E:01A1 B40A          MOV     AH,0A
241E:01A3 CD21          INT     21
241E:01A5 42            INC     DX
241E:01A6 8BF2          MOV     SI,DX
241E:01A8 AC            LODSB
241E:01A9 1C0A          SBB     AL,0A
241E:01AB 754D          JNZ     01FA
241E:01AD 90            NOP
241E:01AE 90            NOP
241E:01AF 2BC0          SUB     AX,AX
241E:01B1 33D2          XOR     DX,DX
241E:01B3 46            INC     SI
241E:01B4 BFE101        MOV     DI,01E1
241E:01B7 BBA59D        MOV     BX,9DA5
241E:01BA B90B00        MOV     CX,000B
241E:01BD 51            PUSH    CX
241E:01BE F8            CLC
241E:01BF AC            LODSB
241E:01C0 750F          JNZ     01D1
241E:01C2 90            NOP
...
241E:01D1 02C3          ADD     AL,BL
241E:01D3 72BB          JB      0190
241E:01D5 0FAFCB        IMUL    CX,BX
241E:01D8 02CD          ADD     CL,CH
241E:01DA 2AC1          SUB     AL,CL
241E:01DC AA            STOSB
241E:01DD 750D          JNZ     01EC
...
241E:01EC 86DF          XCHG    BL,BH
241E:01EE 59            POP     CX
241E:01EF E2CC          LOOP    01BD
241E:01F1 FD            STD
241E:01F2 BECF01        MOV     SI,01CF
241E:01F5 B90D00        MOV     CX,000D
241E:01F8 F3            REPZ
241E:01F9 A6            CMPSB
241E:01FA B409          MOV     AH,09
241E:01FC BA0802        MOV     DX,0208
241E:01FF 7425          JZ      0226
241E:0201 90            NOP
241E:0202 90            NOP
241E:0203 CD21          INT     21
241E:0205 EB18          JMP     021F
...
241E:021F B8004C        MOV     AX,4C00
241E:0222 CD21          INT     21
...
241E:0226 BA6B01        MOV     DX,016B
241E:0229 EBD8          JMP     0203

(The code segment will likely be different on your machine.) Now, examine the code and trace through it with a debugger. Consider the following section of the code:

241E:0197 B409          MOV     AH,09
241E:0199 BA0D01        MOV     DX,010D
241E:019C CD21          INT     21
241E:019E BA8001        MOV     DX,0180
241E:01A1 B40A          MOV     AH,0A
241E:01A3 CD21          INT     21

If we look at the contends of address 010Dh, we see it contains the introductory text. The first INT 21 call prints this text to the screen. The second INT 21 call gets the user input and saves it at 0180h. If you dump the contents of memory starting at 0180h, you will see your input. Now look at the next few lines:

241E:01A5 42            INC     DX
241E:01A6 8BF2          MOV     SI,DX
241E:01A8 AC            LODSB
241E:01A9 1C0A          SBB     AL,0A
241E:01AB 754D          JNZ     01FA

INC DX will set DX equal to 0180h+1h = 0181h. The contents of 0181h will be the length of the user's input (when interrupt 21 saves the user input, it precedes the data with the length of the user input). Thus, the next three lines check whether the user entered 0ah or 10 characters for the password. If the user did not, the computer will jump to 01fah which, upon examination, prints out "Incorrect Password" and terminates the program. We just learned our first clue: the correct password is 10 characters long.



Look at the next few lines:

241E:01AD 90            NOP
241E:01AE 90            NOP
241E:01AF 2BC0          SUB     AX,AX
241E:01B1 33D2          XOR     DX,DX
241E:01B3 46            INC     SI
241E:01B4 BFE101        MOV     DI,01E1
241E:01B7 BBA59D        MOV     BX,9DA5
241E:01BA B90B00        MOV     CX,000B
241E:01BD 51            PUSH    CX
241E:01BE F8            CLC
241E:01BF AC            LODSB
241E:01C0 750F          JNZ     01D1

After clearing the AX and DX registers, SI is incremented. SI should now be pointing to the SECOND character of our input (remember this fact for later). This is because the preceding LODSB command already increased SI from 181h to 182h. SI is now 183h, which is where our second input charater is. The next few lines initialize some registers.

Note that DI is initialized to 01E1h. The LODSB will load AL with our second input character. If that character is not equal to zero (which it shouldn't unless you used escape codes in your input), we will jump to 01D1h. Now comes the interesting part. Examine the next piece of code:

241E:01D1 02C3          ADD     AL,BL
241E:01D3 72BB          JB      0190
241E:01D5 0FAFCB        IMUL    CX,BX
241E:01D8 02CD          ADD     CL,CH
241E:01DA 2AC1          SUB     AL,CL
241E:01DC AA            STOSB
241E:01DD 750D          JNZ     01EC
...
241E:01EC 86DF          XCHG    BL,BH
241E:01EE 59            POP     CX
241E:01EF E2CC          LOOP    01BD
241E:01F1 FD            STD
241E:01F2 BECF01        MOV     SI,01CF
241E:01F5 B90D00        MOV     CX,000D
241E:01F8 F3            REPZ
241E:01F9 A6            CMPSB
Here's the code for the JB instruction at 01D3:

241E:0190 F6D8          NEG     AL
241E:0192 FEC3          INC     BL
241E:0194 EB3F          JMP     01D5

Hmmm. This looks like an encryption procedure. It's modifying each of our input characters according to some algorithm and storing it beginning at 01E1h (recall that DI was initialized to 01E1h). After the encryption (i.e. starting at address cs:01F1h), you will see that the program compares our encrypted password to the something (REPZ CMPSB). Here is where the program checks to see whether we entered the correct password. Now, let's figure out where the correct password is stored. Since the direction flag is set (STD instruction at cs:01F1h), we know that the REPZ CMPSB will compare bytes BACKWARDS from memory. Thus, we know that the last character of the correct password is at ds:01CFh, which is what the SI register is initialized to at cs:01F2h. The CX register is initialized to Dh right before the comparison. This means that the REPZ CMPSB instructions will compare a maximum of Dh characters. Therefore, the beginning of the correct password is at ds:01CFh - Dh + 1h = 01C3. Well, let's see what's there by dumping the contents of memory beginning at ds:01C3:


241E:01C3 90 0A 56 7B D9 21 56 8A-CF F5 1F 61 86 34 02 C3

There's only one problem. That data isn't exactly the correct password. It's the ENCRYPTED correct password. Remember that the program first encrypts our input and then compares our ENCRYPTED input with the above data. Now, you might think, "that's simple, let's just decrypt the data". You're on the right track but, unfortunately, decrypting the password is much more complicated that you probably think.

By now, you must be thinking, if we just change CS:01FF from JZ 0226 to JMP 0226, the program will say "Correct Password" for anything we type in. Sure, we could do that, but kOUGER!'s challenge was for us to to find out what the real password is.

To figure out what the password is, we must COMPLETELY understand what the encryption algorithm is. So, sit back and examine the code carefully. Examine it until you FULLY understand what's going on. First, one character of our input is loaded into AL. Next, BL is added to it. Then, if the addition caused an unsigned overflow (i.e. if AL + BL > 255 causing the carry flag to be set), AL is negated and BL is incremented by one. Next, the sum of the high and low bytes of CX*BX is subtracted from AL to complete the encryption. After storing our encrypted character, the high and low bytes of BX are exchanged and the next character goes through the same process. Let's formalize this algorithm mathematically. First, define a function f(x) which returns the sum of the high and low bytes of its argument, x.
For example:

f(2FB4h) = 2Fh + B4h = E3h.

Here's how the encryption alorithm looks:

If AL + BL <= 255:                      If AL + BL > 255:
output = input + BL - f(BX*CX)  output = -(input+BL) - f[(BX+1)*CX]

Let's re-arrange the equations to solve for the input (i.e. this is the decryption algorithm):

If AL + BL <= 255:                      If AL + BL > 255:
input = output - BL + f(BX*CX)  input = -(output - f[(BX+1)*CX])-BL

No problem, you say. Just substitute the values for the correct encrypted password for "output" and solve for "input". While this seems simple, it has two problems. Here's the first problem: given BX and CX, there are up to two possible inputs which would yield the same output. One for when AL + BL <= 255 and the jump at CS:01D3h is not taken, and one for when AL + BL > 255 and jump at CS:01D3h is taken. For example, if we want to find the correct input for 0Ah (i.e. the second character of the correct encrypted password), we get either 42h or 39h which corresponds to "B" and "9" respectively. We could choose either one and proceed to decode the next character. The problem with this is that if we chose the wrong one, we may find that the later characters we decode fall outside the typeable range of ASCII characters. If we are VERY lucky and chose the correct path through the decryption, we will get the correct password. However, for 10 characters, there are 2^10 = 1024 different paths. That makes this option a little unattractive. The second problem however renders this option even less attractive.

The astute readers will notice that the correct password is 10 characters in length whereas the encryption and compare routines encrypt and compare 12 characters. Thus, the encryption procedure also encrypts the two bytes following our 10 character input beginning at address DS:0182h. Upon examination, we see that the program fixes these two bytes to 0Dh and 00. When these two bytes are encrypted, they will be compared to 1Fh and 61h (check the dump of the correct encrypted password to see where these numbers come from). To fully understand what this means, you have to realize that how each character is encrypted depends on how the previous characters were encrypted. This is because the BX register is changed according to whether the jump at CS:01D3h is taken. Thus, the correct password is one which follows a path which causes BX to equal a value which will correctly encode the 11th and 12th bytes to 1Fh and 61h, respectively.

Now, not only do we have to worry about taking a path which yields an ASCII typeable password, but we must also make sure that it sets BX to a value which correctly encodes the next two bytes. Solution: we will write a program which will go through all the possible paths and see whether one password gives us the same encryption as the correct encrypted password. Before going any further, recall the encryption procedure begins with the SECOND input character, not the first. Thus, the first character of our input is never encrypted at all. Although the password compare procedure actually compares the address where our first input character would have been stored had it been encrypted, we note that that address is fixed at 90h. Fortunately, 90h is exactly what it is compared to in the password compare procedure. This gives us the second clue to the password: it doesn't matter what you type for the first character.

Alright, here's the pascal program which goes through all the possible 2^9 = 512 (remember, the first character is irrelevant) paths. It will print out only those passwords which meet both of the following criteria: (1) All characters fall within the typeable ASCII range (2) Characters 11 and 12 (i.e. those "hard-wired" to 0Dh and 00) are correctly encoded to 1Fh and 61h, respectively. Note also that this program prints only characters 2 to 10 of the correct password(s) as the first character is irrelevant.

Program RbsTrialDecode (Input,Output);

Function LowByte(Value : word) : word;
Begin
  LowByte := Value - (Value div 256)*256;
End;

Function HighByte(Value : word) : word;
Begin
  HighByte := (Value - LowByte(Value)) div 256;
End;

Function Encode(Var InBX : word;
                InCX : word;
                CorrectInput : byte) : byte;
Var
  BX,
  CX            : word;
  CorrectOutput,
  BH,
  BL,
  CH,
  CL,
  TempByte      : byte;

Begin
  BX := InBX;
  CX := InCX;
  BH := HighByte(InBX);
  BL := LowByte(InBX);
  CorrectOutPut := CorrectInput + BL;
  If (CorrectOutput < CorrectInput) Then
  Begin
    CorrectOutput := (CorrectOutput XOR 255) + 1;
    BL := BL + 1;
  End;
  BX := 256*BH + BL;
  CX := CX*BX;
  CL := LowByte(CX);
  CH := HighByte(CX);
  CorrectOutput := CorrectOutput - CH - CL;
  BH := HighByte(BX);
  BL := LowByte(BX);
  BX := 256*BL + BH;
  InBX := BX;
  Encode := CorrectOutput;
End;

Function Decode(Var InBX      : word;
                InCX          : word;
                CorrectOutput : byte;
                NoJump        : boolean) : Byte;

Var

  BX,
  CX  : word;
  CorrectInput     : byte;
  BH,
  BL,
  CH,
  CL,
  TempByte   : byte;

Begin
  If NoJump Then
  Begin
    CX := InCX;
    BX := InBX;
    BL := LowByte(InBX);
    BH := HighByte(InBX);
    CX := CX * BX;
    CL := LowByte(CX);
    CH := HighByte(CX);
    TempByte := CL + CH;
    CorrectInput := CorrectOutput + TempByte - BL;
    BX := BH + 256*BL;
    TempByte := CorrectInput + BL;
    If TempByte >= CorrectInput Then
    Begin
      Decode := CorrectInput;
      InBX := BX;
    End
    Else
      Decode := 0;
  End
  Else
  Begin
    CX := InCX;
    BX := InBX;
    BL := LowByte(BX);
    BH := HighByte(BX);
    BL := BL + 1;
    BX := BL + BH*256;
    CX := BX * CX;
    CL := LowByte(CX);
    CH := HighByte(CX);
    TempByte := CL+CH;
    CorrectInput := ((CorrectOutput + TempByte) XOR 255) + 1 - 
LowByte(InBX);
    BX := BH + 256*BL;
    TempByte := CorrectInput + LowByte(InBX);
    If TempByte < CorrectInput Then
    Begin
      Decode := CorrectInput;
      InBX := BX;
    End
    Else
      Decode := 0;
  End;
End;


Const
  EncodedPassword : array[2..11] of byte = 
(31,245,207,138,86,33,217,123,86,10);

Var
  InBX,
  InCX,
  Counter1,
  Counter2,
  Mask,
  Temp           : word;
  CorrectInput,
  a,b            : byte;
  GoodPassword,
  NoJump         : boolean;
  PasswordString : String;

Begin
  Writeln;
  For Counter1 := 0 to 511 do
  Begin
    Mask := 1;
    InBX := 40357;
    GoodPassword := True;
    PasswordString := '';
    For Counter2 := 1 to 9 do
    Begin
      Temp := Counter1 and Mask;
      NoJump := Temp <> 0;
      InCX := 12 - Counter2;
      CorrectInput := Decode(InBX,InCX,EncodedPassword[InCX],NoJump);
      If (CorrectInput <= 126) and (CorrectInput >= 32) Then
        PasswordString := PasswordString + Chr(CorrectInput)
      Else
      Begin
        PasswordString := PasswordString + Chr(CorrectInput);
        GoodPassword := False;
      End;
      Mask := Mask * 2;
    End {for};
    a := Encode(InBX,2,13);
    b := Encode(InBX,1,0);
    If GoodPassword and (a = 31) and (b = 97) Then
      Writeln(PasswordString);
  End {for};
End.


When we run the program, we see that there are two possible passwords:

BS-Party!
i]5XTrty!

Remember that these are the 2nd to 10th characters and we can enter anything for the first character. It is likely that kOUGER!'s intended password was "RBS-Party!". Running the program and typing these passwords, we see that we have correctly "cracked" the program. Looking back now that I know the password, I think that it may be possible for someone to figure out the password by hand (i.e. without writing a program as I did) if he could see the pattern leading to the word "Party!".



Any questions can be asked to the author at
arasha@asu.edu 1