HOW TO CRACK, by +ORC, A TUTORIAL
LESSON C (2) - How to crack, Cracking as an art
[INSTANT ACCESS]

      cracking Instant Access (2) - strainer for the +HCU

[SEE LESSON C.1 for the first part of this cracking session]
     Here follow the relevant protection routines for the first
(The "Registration") number_code of Instant Access, with my
comments: you have to investigate a little the following code.
     Later, when you'll crack on your own, try to recognize the
many routines that fiddle with input BEFORE the relevant (real
protection) one. In this case, for instance, a routine checks the
correctness of the numbers of your input:

This_loop_checks_that_numbers_are_numbers:
1B0F:2B00 C45E06    LES    BX,[BP+06]  ; set/reset pointer
1B0F:2B03 03DF      ADD    BX,DI
1B0F:2B05 268A07    MOV    AL,ES:[BX]  ; get number
1B0F:2B08 8846FD    MOV    [BP-03],AL  ; store
1B0F:2B0B 807EFD30  CMP    BYTE PTR [BP-03],30
1B0F:2B0F 7C06      JL     2B17        ; less than zero?
1B0F:2B11 807EFD39  CMP    BYTE PTR [BP-03],39
1B0F:2B15 7E05      JLE    2B1C        ; between 0 & 9?
1B0F:2B17 B80100    MOV    AX,0001     ; no, set flag=1
1B0F:2B1A EB02      JMP    2B1E        ; keep flag
1B0F:2B1C 33C0      XOR    AX,AX       ; flag=0
1B0F:2B1E 0BC0      OR     AX,AX       ; is it zero?
1B0F:2B20 7507      JNZ    2B29        ; flag NO jumps away
1B0F:2B22 8A46FD    MOV    AL,[BP-03]  ; Ok, get number
1B0F:2B25 8842CC    MOV    [BP+SI-34],AL ; Ok, store number
1B0F:2B28 46        INC    SI          ; inc storespace
1B0F:2B29 47        INC    DI          ; inc counter
1B0F:2B2A C45E06    LES    BX,[BP+06]  ; reset pointer
1B0F:2B2D 03DF      ADD    BX,DI       ; point next number
1B0F:2B2F 26803F00  CMP    BYTE PTR ES:[BX],00 ; input end?
1B0F:2B33 75CB      JNZ    2B00        ; no:loop next num

     You now obviously understand that the "real" string is
stored inside memory location [BP+SI-34]... set a memory
breakpoint on this area to get the next block of code that
fiddles with the transformed input. Notice how this routine
"normalizes" the input, strips the "-" off and puts the 10
numbers together:
user input:  1  2  1  2  1  2  1  2  1  2 End
  1E7F:92E2 31 32 31 32 31 32 31 32 31 32 00 45 AF 1F 70 9B
 Stack ptr:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
     Let's now look at the "real" protection routine: the one

that checks these numbers and throw you out if they are not
"sound". Please pay attention to the following block of code:

check_if_sum_other_9_numbers_=_remainder_of_the_third_number:
:4B79 8CD0       MOV    AX,SS ; we'll work inside the stack...
:4B7B 90         NOP
:4B7C 45         INC    BP
:4B7D 55         PUSH   BP    ; save real BP
:4B7E 8BEC       MOV    BP,SP ; BP = stackpointer
:4B80 1E         PUSH   DS    ; save real Datasegment
:4B81 8ED8       MOV    DS,AX ; Datasegment = stacksegment
:4B83 83EC04     SUB    SP,+04
:4B86 C45E06     LES    BX,[BP+06] ; BX points input_start
:4B89 268A07     MOV    AL,ES:[BX] ; load first number
:4B8C 98         CBW               ; care only for low
:4B8D C45E06     LES    BX,[BP+06] ; reset pointer
:4B90 50         PUSH   AX         ; save 1st number
:4B91 268A4701   MOV    AL,ES:[BX+01] ; load 2nd number
:4B95 98         CBW               ; only low
:4B96 8BD0       MOV    DX,AX      ; 2nd number in DX
:4B98 58         POP    AX         ; get 1st number
:4B99 03C2       ADD    AX,DX      ; sum with second
:4B9B C45E06     LES    BX,[BP+06] ; reset pointer
:4B9E 50         PUSH   AX         ; save sum
:4B9F 268A4707   MOV    AL,ES:[BX+07] ; load 8th number
:4BA3 98         CBW               ; only low
:4BA4 8BD0       MOV    DX,AX      ; 8th number in DX
:4BA6 58         POP    AX         ; old sum is back
:4BA7 03C2       ADD    AX,DX      ; sum 1+2+8
:4BA9 C45E06     LES    BX,[BP+06] ; reset pointer
:4BAC 50         PUSH   AX         ; save sum
:4BAD 268A4703   MOV    AL,ES:[BX+03] ; load 4rd number
:4BB1 98         CBW               ; only low
:4BB2 8BD0       MOV    DX,AX      ; #4 in DX
:4BB4 58         POP    AX         ; sum is back
:4BB5 03C2       ADD    AX,DX      ; sum 1+2+8+4
:4BB7 C45E06     LES    BX,[BP+06] ; reset pointer
:4BBA 50         PUSH   AX         ; save sum
:4BBB 268A4704   MOV    AL,ES:[BX+04] ; load 5th number
:4BBF 98         CBW               ; only low
:4BC0 8BD0       MOV    DX,AX      ; #5 in DX
:4BC2 58         POP    AX         ; sum is back
:4BC3 03C2       ADD    AX,DX      ; 1+2+8+4+5
:4BC5 C45E06     LES    BX,[BP+06] ; reset pointer
:4BC8 50         PUSH   AX         ; save sum
:4BC9 268A4705   MOV    AL,ES:[BX+05] ; load 6th number
:4BCD 98         CBW               ; only low

:4BCE 8BD0       MOV    DX,AX      ; #6 in DX
:4BD0 58         POP    AX         ; sum is back
:4BD1 03C2       ADD    AX,DX      ; 1+2+8+4+5+6
:4BD3 C45E06     LES    BX,[BP+06] ; reset pointer
:4BD6 50         PUSH   AX         ; save sum
:4BD7 268A4706   MOV    AL,ES:[BX+06] ; load 7th number
:4BDB 98         CBW               ; only low
:4BDC 8BD0       MOV    DX,AX      ; #7 in DX
:4BDE 58         POP    AX         ; sum is back
:4BDF 03C2       ADD    AX,DX      ; 1+2+8+4+5+6+7
:4BE1 C45E06     LES    BX,[BP+06] ; reset pointer
:4BE4 50         PUSH   AX         ; save sum
:4BE5 268A4708   MOV    AL,ES:[BX+08] ; load 9th number
:4BE9 98         CBW               ; only low
:4BEA 8BD0       MOV    DX,AX      ; #9 in DX
:4BEC 58         POP    AX         ; sum is back
:4BED 03C2       ADD    AX,DX      ; 1+2+8+4+5+6+7+9
:4BEF C45E06     LES    BX,[BP+06] ; reset pointer
:4BF2 50         PUSH   AX         ; save sum
:4BF3 268A4709   MOV    AL,ES:[BX+09] ; load 10th #
:4BF7 98         CBW               ; only low
:4BF8 8BD0       MOV    DX,AX      ; #10 in DX
:4BFA 58         POP    AX         ; sum is back
:4BFB 03C2       ADD    AX,DX      ; 1+2+8+4+5+6+7+9+10
:4BFD 0550FE     ADD    AX,FE50    ; clean sum to 0-51
:4C00 BB0A00     MOV    BX,000A    ; BX holds 10
:4C03 99         CWD               ; only AL
:4C04 F7FB       IDIV   BX         ; remainder in DX
:4C06 C45E06     LES    BX,[BP+06] ; reset pointer
:4C09 268A4702   MOV    AL,ES:[BX+02] ; load now # 3
:4C0D 98         CBW               ; only low
:4C0E 05D0FF     ADD    AX,FFD0    ; clean # 3 to 0-9
:4C11 3BD0       CMP    DX,AX  ; remainder = pampered #3?

:4C13 7407       JZ     4C1C       ; yes, go on good guy
:4C15 33D2       XOR    DX,DX  ; no! beggar off! Zero DX
:4C17 33C0       XOR    AX,AX  ;     and FLAG_AX = FALSE
:4C19 E91701     JMP    4D33       ; go to EXIT
let's_go_on_if_first_check_passed:
:4C1C C45E06     LES    BX,[BP+06] ; reset pointer
:4C1F 268A4701   MOV    AL,ES:[BX+01] ; now load #2 anew
:4C23 98         CBW               ; only low
:4C24 05D7FF     ADD    AX,FFD7    ; pamper adding +3
:4C27 A38D5E     MOV    [5E8D],AX  ; save SEC_+3
:4C2A 3D0900     CMP    AX,0009    ; was it < 9? (no A-F)
:4C2D 7E05       JLE    4C34       ; ok, no 0xletter
:4C2F 832E8D5E0A SUB    WORD PTR [5E8D],+0A ; 0-5 if A-F
:4C34 C45E06     LES    BX,[BP+06] ; reset pointer
:4C37 268A07     MOV    AL,ES:[BX] ; load 1st input number
:4C3A 98         CBW               ; only low
:4C3B 05C9FF     ADD    AX,FFC9    ; pamper adding +7
:4C3E A38F5E     MOV    [5E8F],AX  ; save it in FIR_+7
:4C41 0BC0       OR     AX,AX      ; if #1 > 7
:4C43 7D05       JGE    4C4A       ; no need to add 0xA
:4C45 83068F5E0A ADD    WORD PTR [5E8F],+0A ; FIR_+7 + 0xA
now_we_have_the_sliders_let's_prepare_for_loop:

:4C4A C45E0E     LES    BX,[BP+0E] ; Set pointer to E
:4C4D 26C747020000 MOV  WORD PTR ES:[BX+02],0000 ; 0 flag
:4C53 26C7070000   MOV  WORD PTR ES:[BX],0000    ; 0 flag
:4C58 C706975E0900 MOV  WORD PTR [5E97],0009     ; counter=9
:4C5E E99500     JMP    4CF6       ; Jmp check_counter
loop_8_times:
:4C61 C45E06     LES    BX,[BP+06] ; reset pointer
:4C64 031E975E   ADD    BX,[5E97]  ; add running counter
:4C68 268A07     MOV    AL,ES:[BX] ; load # counter+1
:4C6B 98         CBW               ; only low
:4C6C 50         PUSH   AX         ; save 10th number
:4C6D A18D5E     MOV    AX,[5E8D]  ; ld SEC_+3 down_slider
:4C70 BA0A00     MOV    DX,000A    ; BX holds 0xA
:4C73 F7EA       IMUL   DX         ; SEC_+3 * 0xA
:4C75 03068F5E   ADD    AX,[5E8F]  ; plus FIR_+7 up_slider
:4C79 BAA71E     MOV    DX,1EA7    ; fixed segment
:4C7C 8BD8       MOV    BX,AX ; BX = Lkup_val=(SEC_+3*10+FIR_+7)
:4C7E 8EC2       MOV    ES,DX      ; ES = 1EA7
:4C80 268A870000 MOV    AL,ES:[BX+0000] ; ld 1EA7:[Lkup_val]
:4C85 98         CBW               ; only low: KEY_PAR
:4C86 8BD0       MOV    DX,AX      ; save KEY_PAR in DX
:4C88 58         POP    AX         ; repops 10th number
:4C89 03C2       ADD    AX,DX      ; RE_SULT=KEY_PAR+#10
:4C8B 05D0FF     ADD    AX,FFD0    ; polish RE_SULT
:4C8E 99         CWD               ; only low: RE_SULT
:4C8F 8956FC     MOV    [BP-04],DX ; save here KEY_PAR [9548]
:4C92 8946FA     MOV    [BP-06],AX ; save here RE_SULT [9546]
:4C95 0BD2       OR     DX,DX      ; KEY_PAR < 0?
:4C97 7C0F       JL     4CA8       ; yes: KEY_PAR < 0
:4C99 7F05       JG     4CA0       ; no: KEY_PAR > 0
:4C9B 3D0900     CMP    AX,0009    ; KEY_PAR = 0
:4C9E 7608       JBE    4CA8 ; no pampering if RE_SULT < 9
:4CA0 836EFA0A   SUB    WORD PTR [BP-06],+0A ; else pamper
:4CA4 835EFC00   SBB    WORD PTR [BP-04],+00 ; and SBB [9548]
:4CA8 C45E0E     LES    BX,[BP+0E] ; reset pointer to E
:4CAB 268B4F02   MOV    CX,ES:[BX+02] ; charge CX [958C]
:4CAF 268B1F     MOV    BX,ES:[BX] ; charge BX slider [958A]
:4CB2 33D2       XOR    DX,DX      ; clear DX to zero
:4CB4 B80A00     MOV    AX,000A    ; 10 in AX
:4CB7 9A930D2720 CALL   2027:0D93  ; call following RO_routine

  This is the only routine called from our protection, inside the
loop (therefore 8 times), disassembly from WCB. Examining this
code please remember that we entered here with following
configuration: DX=0, AX=0xA, CX=[958C] and BX=[958A]...
 1.0D93  56      push   si     ; save si
 1.0D94  96      xchg   ax, si ; ax=si, si=0xA
 1.0D95  92      xchg   ax, dx ; dx=0xA ax=dx
 1.0D96  85C0    test   ax, ax ; TEST this zero
 1.0D98  7402    je     0D9C   ; zero only 1st time
 1.0D9A  F7E3    mul    bx     ; BX slider! 0/9/5E/3B2...
 1.0D9C >E305    jcxz   0DA3   ; cx=0? don't multiply!
 1.0D9E  91      xchg   ax, cx ; cx !=0? cx = ax & ax = cx
 1.0D9F  F7E6    mul    si     ;     ax*0xA in ax
 1.0DA1  03C1    add    ax, cx ; ax=  ax*0xA+cx = M_ULT
 1.0DA3 >96      xchg   ax, si ; ax=0xA; si evtl. holds M_ULT
 1.0DA4  F7E3    mul    bx     ; ax= bx*0xA
 1.0DA6  03D6    add    dx, si ; dx= dx_add
 1.0DA8  5E      pop    si     ; restore si
 1.0DA9  CB      retf          ; back to caller with two
                                        parameters: DX and AX
Back_to_main_protection_loop_from_RO_routine:
:4CBC C45E0E     LES    BX,[BP+0E] ; reset pointer
:4CBF 26895702   MOV    ES:[BX+02],DX ; save R_DX par  [958C]
:4CC3 268907     MOV    ES:[BX],AX ; save R_AX par     [958A]
:4CC6 0346FA     ADD    AX,[BP-06] ; add to AX RE_SULT [9546]
:4CC9 1356FC     ADC    DX,[BP-04] ; add to DX KEY_PAR [9548]
:4CCC C45E0E     LES    BX,[BP+0E] ; reset pointer
:4CCF 26895702   MOV    ES:[BX+02],DX ; save R_DX+KEY_PAR [958C]

:4CD3 268907     MOV    ES:[BX],AX ; save R_AX+RE_SULT    [958A]
:4CD6 FF0E8D5E   DEC    WORD PTR [5E8D] ; down_slide SEC_+3
:4CDA 7D05       JGE    4CE1       ; no need to add
:4CDC 83068D5E0A ADD    WORD PTR [5E8D],+0A  ; pamper adding 10
:4CE1 FF068F5E   INC    WORD PTR [5E8F] ; up_slide FIR_+7
:4CE5 A18F5E     MOV    AX,[5E8F]  ; save upslided FIR_+7 in AX
:4CE8 3D0900     CMP    AX,0009    ; is it over 9?
:4CEB 7E05       JLE    4CF2       ; no, go on
:4CED 832E8F5E0A SUB    WORD PTR [5E8F],+0A ; yes, pamper -10
:4CF2 FF0E975E   DEC    WORD PTR [5E97]  ; decrease loop counter
check_loop_counter:
:4CF6 833E975E03 CMP    WORD PTR [5E97],+03  ; counter = 3?
:4CFB 7C03       JL     4D00       ; finish if counter under 3
:4CFD E961FF     JMP    4C61       ; not yet, loop_next_count
loop_is_ended:
:4D00 C45E06     LES    BX,[BP+06] ; reset pointer to input
:4D03 268A4701   MOV    AL,ES:[BX+01] ; load 2nd number (2)
:4D07 98         CBW               ; only low
:4D08 05D0FF     ADD    AX,FFD0    ; clean it
:4D0B BA0A00     MOV    DX,000A    ; DX = 10
:4D0E F7EA       IMUL   DX         ; AX = SEC_*10 = 14
:4D10 C45E06     LES    BX,[BP+06] ; reset pointer
:4D13 50         PUSH   AX         ; save SEC_*10
:4D14 268A07     MOV    AL,ES:[BX] ; load 1st number (1)
:4D17 98         CBW               ; only low
:4D18 8BD0       MOV    DX,AX      ; save in DX
:4D1A 58         POP    AX         ; get SEC_*10
:4D1B 03C2       ADD    AX,DX      ; sum SEC_*10+1st number
:4D1D 05D0FF     ADD    AX,FFD0    ; clean it
:4D20 99         CWD               ; only low
:4D21 C45E0A     LES    BX,[BP+0A] ; get pointer    to   [9582]
:4D24 26895702   MOV    ES:[BX+02],DX ; save 1st (1) in  [9584]
:4D28 268907     MOV    ES:[BX],AX ; save FINAL_SUM (15) [9582]
:4D2B 33D2       XOR    DX,DX      ; DX = 0
:4D2D B80100     MOV    AX,0001    ; FLAG TRUE !
:4D30 E9E6FE     JMP    4C19       ; OK, you_are_a_nice_guy
EXIT:
:4D33 59         POP    CX         ; pop everything and
:4D34 59         POP    CX         ;  return with flag
:4D35 1F         POP    DS         ;  AX=TRUE if RegNum OK
:4D36 5D         POP    BP         ;  with 1st # in     [9584]
:4D37 4D         DEC    BP         ;  with FINAL_SUM in [9582]
:4D38 CB         RETF

  Let's translate the preceding code: first of all the pointers:
At line :4B86 we have the first of a long list of stack ptrs:
                        LES BX,[BP+06]
 This stack pointer points to the beginning of the input string,
which, once polished from the "-", has now a length of 10 bytes,
concluded by a 00 fence. At the beginning, before the main loop,
9 out of our 10 numbers are added, all but the third one.
  Notice that protection has jumped # 3 (and added # 8 out of the
line). The rest is straightforward. Now, at line :4BFD we have
our first "cleaning" instruction. You see: the numbers are
hexadecimal represented by the codes 0x30 to 0x39. If you add
FE50 to the minimum sum you can get adding 9 numbers (0x30*9 =
0x160) You get 0. The maximum you could have adding 9 numbers,
on the contrary is (0x39*9=0x201), which, added to FE50 gives
0x51. So we'll have a "magic" number between 0x0 and 0x51 instead
of a number between 0x160 and 0x201. Protection pampers this
result, and retains only the last ciffer: 0-9.  Then protection
divides this number through 0xA, and what happens? DX get's the
REMAINDER of it.
  If we sum the hexcodes of our (1212-1212-12) we get 0x1BE (we
sum only 9 out of then numbers: the third "1" -i.e. "31"- does
not comes into our count); 0x1BE, cleaned and pampered gives E.
Therefore (0xE/0xA = 1) We get 1 with a remainder of 4.
  You may observe that of all possible answers, only sums
finishing with A, B, C, D, E or F give 1 (and rem=0,1,2,3,4 or
5). Sums finishing 0 1 2 3 4 5 6 7 8 or 9 give 0 as result and
themselves as reminder. The chance of getting a 0,1,2,3 or 4 are
therefore bigger as the chance of getting a 5, 6, 7, 8 or 9. We
are just observing... we do not know yet if this should play a
role or not.
  Now this remainder is compared at :4C11 with the third number
polished from 0x30-0x39 to 0-9. This is the only protection check
for the registration number input: If your third number does not
match with the remainder of the sum of all the 9 others numbers
of your input you are immediately thrown out with FLAG AX=FALSE
(i.e. zero).
 To crack the protection you now have to MODIFY your input string
accordingly. Our new input string will from now on be "1242-1212-
12": we have changed our third number (originally a "2") to a "4"
to get through this first strainer in the correct way. Only now
protection starts its mathematical part (We do not know yet why
it does it... in order to seed the random product number? To
provide a check for the registration number you'll input at the
end? We'll see).
-    Protection saves the second number of your input (cleaned
     with FFD7) in SEC_+3 [5E8D], pampering it if it is bigger
     than 9 (i.e. if it is 0xA-0xF). Here you'll have therefore
     following correspondence: 0=7 1=8 2=9 3=0 4=1 5=2 6=3 7=4
     8=5 9=6. The second number of your input has got added +3.
     This is value SEC_+3. In (lengthy) C it would look like
     this:
       If (RegString(2)is lower than  7) RegString(2) = RegString(2)+3
       Else Regstring(2) = ((RegString(2)-10)+3)
-    Protection saves your first number in FIR_+7 [5E8F] with a
     different cleaning parameter (FFC9). The next pampering
     adds 0xA if it was not 7/8/9 therefore you have here
     following correspondence 7=0 8=1 9=2 0=3 1=4 2=5 3=6 4=7
     5=8 6=9). This is value FIR_+7. In (lengthy) C it would
     look like this:
       If (RegString(1) is lower than 3) RegString(1) = RegString(1)+7
       Else Regstring(1) = ((RegString(1)-10)+7)
  So protection has "transformed" and stored in [5E8D] and [5E8F]
the two numbers 1 and 2. In our RegString: 1242-1212-12 the first
two numbers "12" are now stored as "94". These will be used as
"slider" parameters inside the main loop, as you will see.
 Only now does protection begin its main loop, starting from the
LAST number, because the counter has been set to 9 (i.e. the
tenth number of RegString). The loop, as you'll see, handles only
the numbers from 10 to 3: it's an 8-times loop that ends without
handling the first and second number. What happens in this
loop?... Well, quite a lot: Protection begins the loop loading
the number (counter+1) from the RegString. Protection then loads
the SEC_+3 down_slider parameter (which began its life as second
number "transformed"), multiplies it with 0xA and then adds the
up_slider parameter FIR_+7 (at the beginning it was the first
number transformed).
     This sum is used as "lookup pointer" to find a parameter
inside a table of parameters in memory, which are all numbers
between 0 and 9. Let's call this value Lkup_val.
Protection looks for data in 1EA7:[Lkup_val]. In our case (we
entered 1242-1212-12, therefore the first SEC_+3 value is 9 and
the first FIR_+7 value is 4): [Lkup_val] = 9*0xA+4; 0x5A+4 =
0x5E. At line :4C80 therefore AL would load the byte at 1EA7:005E
(let's call it KEY_PAR), which now would be ADDED to the #
counter+1 of this loop. In our case KEY_PAR at 1EA7:005E it's a
"7" and is added to the pampered 0x32=2, giving 9.
     Let's establish first of all which KEY_PAR can possibly get
fetched: the maximum is 0x63 and the minimum is 0x0. The possible
KEY_PARs do therefore dwell in memory between 1EA7: and
1EA7:0063. Let's have a look at the relative table in memory,
where these KEY_PARs are stored ("our" first 0x5Eth byte is
underlined):
1EA7:0000 01 03 03 01 09 02 03 00-09 00 04 03 08 07 04 04
1EA7:0010 05 02 09 00 02 04 01 05-06 06 03 02 00 08 05 06
1EA7:0020 08 09 05 00 04 06 07 07-02 00 08 00 06 02 04 07
1EA7:0030 04 04 09 05 09 06 00 06-08 07 00 03 05 09 00 08
1EA7:0040 03 07 07 06 08 09 01 05-07 04 06 01 04 02 07 01
1EA7:0050 03 01 08 01 05 03 03 01-02 08 02 01 06 05 07 02
1EA7:0060 05 09 09 08 02 09 03 00-00 04 05 01 01 03 08 06
1EA7:0070 01 01 09 00 02 05 05 05-01 07 01 05 08 07 01 09
1EA7:0080 08 07 07 04 04 08 03 00-06 01 09 08 08 04 09 09
1EA7:0090 00 07 05 02 03 01 03 08-06 05 07 06 03 07 06 07
1EA7:00A0 04 02 02 05 02 04 06 02-06 09 09 01 05 02 03 04
1EA7:00B0 04 00 03 05 00 03 08 07-06 04 08 08 02 00 03 06
1EA7:00C0 09 00 00 06 09 04 07 02-00 01 01 01 01 00 01 FF
1EA7:00D0 00 FF FF FF FF 00 FF 01-00 00 00 00 00 00 00 00

     An interesting table, where all the correspondences are
between 0 and 9... are we getting some "secret" number here? But,
hey, look there... funny, isn't it? Instead of only 0-0x63 bytes
we have roughly the DOUBLE here: 0-0xC8 bytes (the 01 sequence
starting at CA "feels" like a fence). We'll see later how
important this is. At the moment you should only "perceive" that
something must be going on with a table that's two time what she
should be.
     As I said the result of KEY_PAR + input number is polished
(with a FFDO) and pampered (subtracting, if necessary, 0xA).
Therefore the result will be the (counter+1) input number +
KEY_PAR (let's call it RE_SULT], in our case, (at the beginning
of the loop) a 9. Now (DX=0 because of the CWD instruction) DX
will be saved in [9548] and RE_SULT in [9546].
 Now Protection prepares for the RO_routine: resets its pointer
and charges CX and BX from [958C] and from [958A] respectively,
charges AX with 0xA and sets DX to zero.
 The routine performs various operations on AX and DX and saves
the results in the above mentioned locations [958A] and [958C].
 Now KEY_PAR and RE_SULT are added respectively to the DX and AX
value we got back from the RO_routine call, and saved once more
in the last two locations: AX+RE_SULT in [958A] and DX+KEY_PAR
in [958C]
 Now the value in SEC_+3 is diminished by 1 (if it was 9 it's now
8, if it was zero it will be pampered to 9). It's a "slider"
parameter (in this case a down_slider), typically used in
relatively complicated protections to give a "random" impression
to the casual observer. The value in FIR_+7, on the contrary, is
augmented by one, from 4 to 5... up_sliding also.
     Protection now handles the next number of your input for the
loop. In our case this loop uses following protection
configuration with our "sliding" parameters:
 Input #  pamp_2nd   pamp_1st   Lookup value  KEY_PAR  # RE_SULT
# 10 = 2, SEC_+3= 9, FIR_+7= 4, Lkup_val = 0x5E, KEY=7 +2 = 9
# 9  = 1, SEC_+3= 8, FIR_+7= 5, Lkup_val = 0x55, KEY=3 +1 = 4
# 8  = 2, SEC_+3= 7, FIR_+7= 6, Lkup_val = 0x4C, KEY=4 +2 = 6
# 7  = 1, SEC_+3= 6, FIR_+7= 7, Lkup_val = 0x43, KEY=7 +1 = 7
# 6  = 2, SEC_+3= 5, FIR_+7= 8, Lkup_val = 0x3A, KEY=0 +2 = 2
# 5  = 1, SEC_+3= 4, FIR_+7= 9, Lkup_val = 0x31, KEY=4 +1 = 5
# 4  = 2, SEC_+3= 3, FIR_+7= 0, Lkup_val = 0x1E, KEY=5 +2 = 7
# 3  = 4, SEC_+3= 2, FIR_+7= 1, Lkup_val = 0x15, KEY=2 +4 = 5
Notice how our "regular" input 21212124 has given an "irregular"
94672575.
     You may legitimately ask yourself what should all this mean:
what are these RE_SULTs used for? Well they are used to slide
another parameter: this one inside the called routine... this is
what happens to AX and DX inside the routine, and the lines after
the called routine:
:4CBF 26895702   MOV    ES:[BX+02],DX ; save R_DX par  [958C]
:4CC3 268907     MOV    ES:[BX],AX ; save R_AX par     [958A]
:4CC6 0346FA     ADD    AX,[BP-06] ; add to AX RE_SULT [9546]
:4CC9 1356FC     ADC    DX,[BP-04] ; add to DX KEY_PAR [9548]
:4CCC C45E0E     LES    BX,[BP+0E] ; reset pointer to E
:4CCF 26895702   MOV    ES:[BX+02],DX ; save R_DX+KEY_PAR [958C]
:4CD3 268907     MOV    ES:[BX],AX ; save R_AX+RE_SULT    [958A]

            :4CC6     :4CC9  :4CCF Odd_DX  :4CD3 slider_sum
  RE_SULT   [958A]    [958C]   [958C]        [958A]

     0         0        0        0            0
     9         5A       0        0            9
     4         3AC      0        0            5E
     6         24F4     0        0            3B2
     7         71CE     1        1            24FB
     2         7220     4        E            71D0
     5         7572     4        90           7225
                                              7579

Now the loops ends, having handled the input numbers from tenth
to third. Protection loads the second number and multiplies it
by 10 (let's call this result SEC_*10), in our case 2*0xA=14.
Protection loads the first number and adds it to the
multiplication, in our case 1+0x14=0x15 (FINAL_SUM].
Now everything will be added to FFDO to "clean" it.
Pointer will now be set to the end of the input number.
DX, zeroed by CDW, will be saved as parameter in [9584] and the
cleaned and pampered sum will be saved in [9582].
FLAG is set to true and this routine is finished! No parameter
are passed and the only interesting thing is what actually
happens in the locations [9582], [9584], [958A] and [958C], i.e.:
FINAL_SUM, 0, slider_sum, odd_dx.
     In the next lesson we'll crack everything, but I'll give you
already some hints here, in case you would like to go ahead on
your own: we'll see how the scheme used for the third (the
registration) number show analogies and differences with the
scheme we have studied (and cracked) here for the first number.
Our 3434-3434-3434-3434-34 input string for the registration
number will be transformed in the magic string
141593384841547431, but this will not work because the "magic"
12th number: "1" will not correspond to the remainder calculated
inside this check through the previous locations of the other
checks.
     Here the things are more complicated because every little
change in your input string transforms COMPLETELY the "magic"
string... therefore in order to pass the strainer you'll have to
change 3434-3434-3434-3434-34 in (for instance) 7434-3434-3434-
3434-96. The "magic" string 219702960974498056 that this
registration input gives will go through the protection strainer.
Only then we'll be able to step over and finally crack the whole
protection... it's a pretty complicated one as I said. Now crack
it pupils... you have three months time. From this crack depends
your admission to the Uni, there will be no other admission text
till summer 1997 (it's a hell of work to prepare this crap)...
work well.

Well, that's it for this lesson, reader. Not all lessons of my
tutorial are on the Web.
     You 'll obtain the missing lessons IF AND ONLY IF you mail
me back (via anon.penet.fi) some tricks of the trade I may not
know but YOU've discovered. I'll probably know most of them
already, but if they are really new you'll be given full credit,
and even if they are not, should I judge that you "rediscovered"
them with your work, or that you actually did good work on them,
I'll send you the remaining lessons nevertheless. Your
suggestions and critics on the whole crap I wrote are also
welcomed.


+ORC an526164@anon.penet.fi