494 lines
26 KiB
Plaintext
494 lines
26 KiB
Plaintext
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 |