Bookmark Magic v2.1 - Tutorial

http://www.cyber-matrix.com/bkmkmag.htm - Webpage (BMg32s21.zip - (819k)).

Its not very often that I praise shareware authors, but in this protection scheme you really must understand the code in order to produce a key generator. Bookmark Magic uses a combination of what I term as a code matrix and a key generator. Start the program and take a look, now there must be a registration option or a place to enter a key (you read that in the help file), but look how do we enter it. Although its a minor irritation, hiding the register option is sneaky. Eventually you'll figure that you need to select About and then click on the red 'Unregistered' text.

Bookmark Magic registration screen

So lets start, I advise you >bpx Hmemcpy and then use F12 and F10 to get to the following code:

:004477E7 CMP DWORD PTR [EBP-04],00000000 <-- Check_name_length_for_0.
:004477EB JNZ 004477FC <-- Jump_code_entered.
:004477FC LEA EDX,[EBP-04]
:004477FF MOV EAX,[EBX+000001E4]
:00447805 CALL 0041CF6C <-- Returns length of name entered.
:0044780A MOV EAX,[EBP-04] <-- Name.
:0044780D PUSH EAX <-- Stack it.
:0044780E LEA EDX,[EBP-08]
:00447811 MOV EAX,[EBX+000001DC]
:00447817 CALL 0041CF6C <-- Returns length of code entered.
:0044781C MOV EAX,[EBP-08] <-- Code.
:0044781F POP EDX <-- Name.
:00447820 CALL 00440C38 <-- Code matrix and calculation routine.
:00447825 TEST AX,AX <-- Test AX for 0 (good code will set AX != 0).
:00447828 JZ 004478B2 <-- Jump_bad_code.

By examining this code fragment this looks an easy patch, just nop away the JZ live in Softice, yes it works, but wait, try restarting and its still unregistered. In fact it is possible with a little zen cracking to patch this code (and 1 other location) but you'll need to feel the disassembly a little.

Just look at the disassembly once again and at this code at the very end of CALL 00440C38:

:00440E8C MOV [EBP-0A], FFFF <-- A flag moved to -1.
:00440E92 XOR EAX,EAX <-- XOR EAX.

Well, we know that we would like this function to return AX non-zero, so just feel the earlier parts of the function, I counted at least 15 conditional jumps to address 00440E92 (just over our flag). You see how it all becomes clear, we need AX set to -1 and now we can just nop away the JZ. However lets zen further, note that this routine is called from 2 locations (0044C99C would seem to be a mirror check (maybe the start up check)), and look there, a TEST AX,AX once again. With this zen you should now be able to patch these 2 locations and beat this scheme once and for all.

I want also to discuss this entire function in terms of a key generator as well. The scheme is fairly clever and will use some interesting tricks to verify our code (all non-significant code has been omitted):

:00440C7B CALL 00403B38
:00440C80 CMP EAX,00000015 <-- Check code length for 15h (21 decimal).
:00440C83 JL 00440E92 <-- Jump_if_code_isn't_22_or_more.
.....
:00440CA1 CALL 00403C48 <-- Compare the first 4 letters of the code with BMAG.
:00440CA6 JNZ 00440E92 <-- Jump_bad_code.

So the first part of this routine will verify the length of our code, use 22 (the minimum requirement), and also the first 4 characters which must be BMAG.

:00440CB8 MOV EDX, DWORD PTR [EBP-04]
:00440CBB MOVZX EDX, BYTE PTR [EDX+EAX-01]
:00440CC0 ADD ESI,EDX <-- Sum values of BMAG in ESI.
:00440CC2 INC EAX <-- Loop counter.
:00440CC3 CMP EAX,00000005 <-- Compare for loop end.
:00440CC6 JNZ 00440CB8 <-- Loop.
:00440CC8 MOV [EBP-1C],ESI
:00440CCB FILD DWORD PTR [EBP-1C]
:00440CCE FMUL DWORD PTR [00440EEC] <-- FPU Maths.
.....
:00440CE0 CMP AL, BYTE PTR [EDX+EBX-01] <-- AL holds maths result (46h).
:00440CE4 JNZ 00440E92 <-- 6th_char_must_be_46h.

Another check, this time the 6th character must be equal to the result of our maths operations on the string BMAG (in this case 46h or the letter F), note that the 5th char will not be checked.

:00440CEA INC EBX <-- EBX now used as a character pointer.
:00440CEB MOV EAX,[EBP-04]
:00440CEE MOV AL, BYTE PTR [EAX+EBX-01] <-- 7th character.
:00440CF2 CMP AL,30 <-- Compare with 30h i.e. 0.
:00440CF4 JB 00440E92 <-- Jump_below_bad.
:00440CFA MOV EDX, DWORD PTR [EBP-04]
:00440CFD CMP AL,39 <-- Compare with 39h i.e. 9.
:00440CFF JA 00440E92 <-- Jump_above_bad_code.

Well I'll skip some code here, the program will now check that the 7th & 8th characters are between 30-39h, i.e. a number, the 8th character is checked using code very similar to the 7th shown above.

:00440D20 INC EBX <-- 9th character.
.....
:00440D42 TEST EDX,EDX <-- Test EDX=0, was a name entered.
:00440D44 JLE 00440D59 <-- Jump_no_name_entered.
:00440D4B MOV ECX, DWORD PTR [EBP-10] <-- Upper_cased_name.
:00440D4E MOVZX ECX, BYTE PTR [ECX+EAX-01] <-- Pointer to name chars.
:00440D53 ADD ESI,ECX <-- ESI used for storing result.
:00440D55 INC EAX
:00440D56 DEC EDX <-- Loop control.
:00440D57 JNZ 00440D4B <-- Loop_name.
:00440D62 MOV EAX,ESI <-- EAX holds sum of name from loop.
:00440D64 POP EDX <-- Length of name.
:00440D65 MOV ECX,EDX <-- Length now in ECX.
:00440D67 CDQ <-- Convert double to quad, so 0 EDX.
:00440D68 IDIV ECX <-- Signed integer division, result is 46h (J).
.....
:00440D71 CMP AL, BYTE PTR [EDX+EBX-01] <-- Compare 9th character with result.
:00440D75 JNZ 00440E92 <-- Bad_guy_jump.

Well this code is critical, the 9th character of the code will act as a checksum for the code, the name will first be uppercased then all the ASCII values will be added before a signed integer division by the length determines the 9th character, for my name the result is J (46h). I'll skip a little more code now, the 10th character will be checked to make sure it is a number.

:00440D96 INC EBX <-- 11th character.
:00440D97 CMP BYTE PTR [EBP-11],4C <-- Check it for 4C (L).
:00440D9B JNZ 00440DC5 <-- Jump_good_guy.

Well this small next piece of code seems to be bugged or just bad implementation, just make sure the 11th character isn't a L and the code will be accepted, which if you examine the code might not have been the authors intention. The 12th & 14th characters must be numbers, they are checked in a similar fashion to previous positions, the 13th character will not be checked (note the 2 INC EBX's).

:00440DFC CMP BYTE PTR [EAX+EBX-01],52 <-- Check 15th character for 52h (R).
:00440E01 JZ 00440E3E <-- Jump_good_code.
:00440E3E INC EBX <-- 16th character (not checked).
:00440E3F INC EBX <-- 17th character now gets checked to make sure its a number.

It seems at this stage the author got lazy and decided not to check some characters, the 16th character is skipped and positions 17,18 & 19 will only be checked for values between 30-39h (numbers). The 20th and 22nd characters will be skipped and with the following sneaky check the program ensures that the 21st character is a number.

:00440E7D MOV AL, BYTE PTR [EAX+EBX-01] <-- Sneaky check on the 21st character.

The 21st character check I've deemed sneaky because the code prior to this skipped the 20th and 22nd positions. Now we have totally examined the scheme we can devise a code matrix and key generator. All one needs to do is work out the check result for the 9th digit for your particular user name, the correct details will be appended to the win.ini file.

Code Matrix: BMAGxF##x#y###Rx###x#x

x = check result from name.
y = anything except L.

17/08/01 : Well I confess to being amazed ;-), there was up until 5 minutes ago a registration name / license code combination here for this 3 year old program ..... and someone finally complained about it ;-). What can I say, what lengths will some people sink too, therefore dear readers, I don't advise you buy this program, I don't advise you even download it (assuming you can even find it) and I certainly don't advise you debug or register it, its quite frankly a load of crap, so don't waste your time ;-).


Return to Miscellaneous


© 1998, 1999, 2000, 2001 CrackZ. 15th June 1998, 17th August 2001.