Description A CAD Viewer.
Protection Type Serial Number / Key Generator.
Target Name CAD Viewer v3.2 A.30. By zoltan.

"Commenting others good tutorials, ah! I could get used to this :-). zoltan's gone to great lengths here to take newbies and the like through this very code-heavy protection scheme, which isn't that weak and features a fair few checksums. I'm pleased also to see that you are left to work a little at the end with only partial source code for a key generator. Several interesting points here for the protection author, see here how very easy it is to actually isolate your protection scheme and then sip a good drink and reverse it at leisure as good reverser zoltan does, see also some very peculiar compiler generated code in parts of your functions, maybe you should check your source code :-).". "Slightly edited by CrackZ, or at least that was the initial intention".

Greetings everyone and welcome to my first public tutorial on how to make a key generator. With this tutorial I am going to show you how to make a keygen for a program called CAD Viewer v3.2. You can grab it from the location above. After we install the program we can start off by running the program, then Enter a name/serial in the registration field's. Next go into SoftICE and set a bpx on GetDlgItemTextA and press the OK button. SoftICE pops up, F12 to return to the caller and we should be landing here :-

: bpx getdlgitemtexta
: F12 (p ret)

:0054CB6B CALL [USER32!GetDlgItemTextA]
:0054CB71 PUSH 3F <-- We are here.
:0054CB73 LEA EAX,[EBP-0194]
:0054CB79 PUSH EAX
:0054CB7A PUSH 000001AC
:0054CB7F MOV EAX,[EBP+08]
:0054CB82 PUSH EAX
:0054CB83 CALL [USER32!GetDlgItemTextA]

:0054CB89 LEA EAX,[EBP-0194] <-- Load effective adress of serial number string into EAX.
:0054CB8F PUSH EAX <-- Push it on the stack.
:0054CB90 LEA EAX,[EBP-01D4] <-- Load effective adress of name string into EAX.
:0054CB96 PUSH EAX <-- Push it on the stack.

OK trace pass where the serial is getting pushed (0054CB90) and set a bpm on ESP (bpm esp). Now hit F5 and we should be landing here :-

:0054CD8F MOV EAX,[EBP+0C] <-- Move serial string offset into EAX.
:0054CD92 PUSH EAX <-- Push it on the stack.
:0054CD93 CALL 0054CFE9 <-- Call a function.

Do the same as above, trace pass where the serial string is being pushed and put a bpm on ESP. Next we land here :-

:0054CFF9 MOV EAX,[EBP+08]
:0054CFFC MOVSX EAX, BYTE PTR [EAX] <-- Move first number of serial into EAX.
:0054CFFF TEST EAX, EAX <-- Test for 0.
:0054D001 JZ 0054D178 <-- If it's equal to zero, then quit :-).
:0054D007 MOV EAX,[EBP+08]
:0054D00A PUSH EAX <-- Push the serial string on the stack again.
:0054D00B CALL 0059DA60 <-- This call is calculating the length of the string.
:0054D010 ADD ESP, 4
:0054D013 MOV [EBP-08],EAX <-- Return value from the call is in EAX, (string length).

All the next algorithm does is check each character of the serial string if they are numbers or not, if it finds that one or more of the chars are not numbers it removes them from the string. Lets move on...

:0054D078 MOV EAX,[EBP-10] <-- Length.
:0054D07B MOV ECX,[EBP+0C] <-- Point to new string.
:0054D082 MOV EAX,[EBP+0C]
:0054D085 PUSH EAX <-- Push serial string offset on stack.
:0054D086 CALL 0054E0DD <-- We'd better trace in here.

A few lines down inside the call...

:0054E0E6 MOV EAX,[EBP+08]
:0054E0E9 PUSH EAX
:0054E0EA CALL 0059DA60 <-- Get length of serial string.
:0054E0EF ADD ESP, 4
:0054E0F2 CMP EAX, 0E <-- Compare it to 14.
:0054E0F5 JAE 0054E100 <-- If it's above or equal, jump to (go away bad cracker) algorithm start.
:0054E0FB JMP 0054E181 <-- Else jump to end of this call.

I am not going to go deeper into the (go away bad cracker) algorithm because it's not important here. Return from the call and we are back here :-

:0054D08E MOV EAX, [EBP+0C] <-- Our serial string.
:0054D091 PUSH EAX
:0054D092 CALL 0059DA60 <-- Get length of serial, return it in EAX.
:0054D097 ADD ESP, 4
:0054D09A MOV [EBP-10],EAX
:0054D09D CMP DWORD PTR [EBP-10],0D <-- If length is 13.
:0054D0A1 JZ 0054D0AC <-- Then jump good boy.
:0054D0A7 JMP 0054D178 <-- Else beggar off bad cracker!.
:0054D0AF MOVSX EAX, BYTE PTR [EAX+01] <-- Second number of serial in EAX.
:0054D0B3 CMP EAX, 31 <-- Compare it to 1.
:0054D0B6 JZ 0054D0D1 <-- Jump good boy.
:0054D0BF MOVSX EAX,BYTE PTR [EAX+02] <-- Third number of serial in EAX.
:0054D0C3 CMP EAX, 33 <-- Compare it to 3.
:0054D0C6 JZ 0054D0D1 <-- Jump good boy.

Alright then, so now we know that the serial needs to be at least 13 chars long and the second number of the serial needs to be equal to 1 or the third char needs to be equal to 3 ..more tagging, lets move on and monitor the third position.

:bc *
:bpx 0054d0c6

We enter a serial (say : 1234555667890). Next we press the OK button and we land in SoftICE again :-

:0054D0D1 MOV EAX,[EBP+0C] <-- Our serial.
:0054D0D4 MOVSX EAX, BYTE PTR [EAX+0A] <-- 11th position of the serial in EAX.
:0054D0D8 LEA EAX,[EAX*4+EAX-00F0]
:0054D0E2 LEA EAX, [EAX*4+EAX]
:0054D0E5 MOV ECX,[EBP+0C]
:0054D0E8 MOVSX ECX, BYTE PTR [ECX+09] <-- 10th position of the serial in ECX.
:0054D0EC LEA ECX,[ECX*4+ECX-00F0]
:0054D0F3 LEA ECX,[ECX*4+ECX]
:0054D0F6 LEA ECX,[ECX*4+ECX]
:0054D0F9 LEA ECX,[ECX*4+ECX]
:0054D0FC SHL ECX, 4

EAX is now 78000 decimal.

:0054D102 MOV ECX,[EBP+0C]
:0054D105 MOVSX ECX,BYTE PTR [ECX+08] <-- 9th position in ECX.
:0054D109 SUB ECX,30
:0054D10E SHL ECX,05
:0054D111 SUB ECX,EDX
:0054D113 LEA ECX,[ECX*4+EDX]
:0054D116 LEA ECX,[ECX*4+ECX]
:0054D119 LEA ECX,[ECX*4+ECX]
:0054D11C SHL ECX,05

EAX is now 678000 decimal.

:0054D121 MOV ECX,[EBP+0C]
:0054D124 MOVSX ECX,BYTE PTR [ECX+0C] <-- Last number of the serial.
:0054D128 LEA ECX,[ECX*4+ECX-00F0]
:0054D12F LEA EAX,[ECX*2+EAX]

EAX is now 678000 decimal, note the bold as this is the last number of our serial.

:0054D132 MOV ECX,[EBP+0C]
:0054D135 MOVSX ECX,BYTE PTR [ECX+07] <-- 8th number of serial.
:0054D139 SUB ECX,30
:0054D13E SHL ECX,06
:0054D141 ADD ECX,EDX
:0054D143 LEA ECX,[ECX*2+ECX]
:0054D146 LEA ECX,[ECX*4+EDX]
:0054D149 LEA ECX,[ECX*4+EDX]
:0054D14C LEA ECX,[ECX*4+ECX]
:0054D14F SHL ECX,06
:0054D152 ADD EAX,ECX

EAX is now 6678000 decimal.

:0054D154 MOV ECX,[EBP+0C]
:0054D157 MOVSX ECX,BYTE PTR [ECX+0B] <-- 12th number of serial.
:0054D15B LEA ECX,[ECX*4-00C0]
:0054D162 LEA ECX,[ECX*4+ECX]
:0054D165 LEA ECX,[ECX*4+ECX]
:0054D168 ADD EAX,ECX

EAX is now 6678900.

:0054D16A MOV ECX,[EBP+0C]
:0054D16D MOVSX ECX,BYTE PTR [ECX] <-- 1st positon of serial number.
:0054D170 SUB ECX,30
:0054D173 ADD EAX,ECX

EAX is now 6678901.

:0054D175 MOV [EBP-04],EAX <-- Store checksum to EBP-04.
:0054D178 MOV EAX,[EBP-04] <-- Very strange.
:0054D17B JMP 0054D180 <-- Very strange again (maybe an inefficient compiler?).
:0054D180 POP EDI
:0054D181 POP ESI
:0054D182 POP EBX
:0054D183 LEAVE
:0054D184 RET

Alright then, the lengthy checksum is finished and the result stored away in [EBP-04]. Now did you understand how our checksum relates to our serial :-

       ||||||\_ First number in our serial.
       |||||\__ 13th.
       ||||\___ 12th.
       |||\____ 11th.
       ||\_____ 10th.
       |\______ 9th.
       \_______ 8th.

OK lets move on ...F12 out of the CALL and you'll be here :-

:0054CD93 CALL 0054CFE9 <-- Where we returned.
:0054CD98 ADD ESP,08
:0054CD9B MOV [EBP-04],EAX <-- Checksum into [EBP-04].
:0054CD9E CMP DWORD PTR [EBP-04],0 <-- Was it 0.

This is the beggar off I was talking about before, EAX is set to zero inside the call if either the serial string was greater or equal to 14 or if the second or third number in the serial was not equal to 1 or 3.

:0054CDA2 JNZ 0054CDBC <-- Jump good boy.
:0054CDA8 CALL 0054D185
:0054CDB7 JMP 0054CE20 <-- Beggar off.
:0054CDBC MOV EAX,[EBP+08] <-- Our name.
:0054CDBF PUSH EAX <-- Push on the stack.
:0054CDC0 MOV EAX,[006CCC20] <-- A checksum which is 2041 decimal, remember this.
:0054CDC5 PUSH EAX <-- Push on the stack.
:0054CDC6 CALL 0054CC7E <-- Let the games begin.

Where the real algorithm begins ...

:0054CC95 MOV EAX,[EBP+08] <-- Our Magic value.
:0054CC98 MOV [EBP-0C],EAX <-- Move it into [EBP-0C].
:0054CC9B MOV EAX,[EBP+0C] <-- Name.
:0054CC9F CALL 005A6F60 <-- Strcpy().
:0054CCA7 PUSH EAX <-- The copy.
:0054CCA8 CALL 005DB460 <-- Strlwr() - Convert to lower case.
:0054CCB0 MOV [EBP-08],EAX <-- Lower cased name.
:0054CCB7 CALL 0059DA60 <-- Get length.
:0054CCC9 JMP 0054CCD1 <-- Jump to loop start.

Here is the main loop :-

:0054CCCE INC DWORD PTR [EBP-10] <-- A counter which was set to 0.
:0054CCD1 MOV EAX,[EBP-10]
:0054CCD4 CMP [EBP-04],EAX <-- Compare counter with length of name.
:0054CCD7 JLE 0054CD40
:0054CCDD MOV EAX,[EBP-10]
:0054CCE0 MOV ECX,[EBP-08] <-- Name.
:0054CCE3 MOVSX EAX,BYTE PTR [ECX+EAX] <-- Move name string char into EAX.
:0054CCE7 MOV [EBP-18],EAX <-- Place in [EBP-18].
:0054CCEA CMP DWORD PTR [EBP-18],61 <-- Compare with 'a'.
:0054CCEE JL 0054CCFE
:0054CCF4 CMP DWORD PTR [EBP-18],7A <-- Compare with 'z'.
:0054CCF8 JLE 0054CD12
:0054CCFE CMP DWORD PTR [EBP-18],30 <-- Compare with '0'.'
:0054CD02 JL 0054CD3B
:0054CD08 CMP DWORD PTR [EBP-18],39 <-- Compare with '9'.
:0054CD0C JG 0054CD3B
:0054CD12 MOV EAX,[EBP-18] <-- Current character.
:0054CD15 IMUL EAX,[EBP-14] <-- Multiply with [EBP-14] (set to 1 before the loop).
:0054CD19 ADD [EBP-0C],EAX <-- Add to [EBP-0C], the checksum.
:0054CD1C MOV EAX,[EBP-14] <-- Move EAX to [EBP-14].
:0054CD1F LEA EAX,[EAX*4+EAX] <-- Multiply by 5.
:0054CD22 ADD EAX,EAX <-- Double it.
:0054CD24 MOV [EBP-14],EAX <-- Restore EAX to [EBP-14].
:0054CD27 CMP DWORD PTR [EBP-14],000186A0 <-- Compare to 100000.
:0054CD2E JLE 0054CD3B
:0054CD34 MOV DWORD PTR [EBP-14],00000001 <-- Else [EBP-14] = 1.
:0054CD3B JMP 0054CCCE <-- Get next letter.

:0054CD40 CMP DWORD PTR [EBP-08],00 <-- Compare [EBP-08] (name) with zero.
:0054CD44 JZ 0054CD56 <-- Beggar off.
:0054CD56 CMP DWORD PTR [EBP-0C],0098967F <-- Compare checksum with 9999999 dec.
:0054CD5D JLE 0054CD6F
:0054CD63 SUB DWORD PTR [EBP-0C],00989680 <-- Else sub 10000000 dec. from checksum.
:0054CD6A JMP 0054CD56 <-- Loop.
:0054CD6F MOV EAX,[EBP-0C] <-- Move new checksum into EAX.
:0054CD7B RET <-- Return.

Now this looks fairly hard to understand :-). Return from the call and we land here :-

:0054CDCE MOV [EBP-0108],EAX <-- Move EAX (Checksum) to [EBP-108].
:0054CDD4 MOV EAX,[EBP-0108] <-- Restore it (strange :p).
:0054CDDA CMP [EBP-04],EAX <-- Compare with checksum from serial number.
:0054CDDD JZ 0054CDED <-- Yes :-).

There we are finished, now I'll show you my keygen source coded in C (but you'll need to add to it :-) ).

   int a,i,len1;
   unsigned char name[100],code_string[100];
   unsigned char table1[30]="x433434xxxxxx";
   unsigned long checksum1=0;
   unsigned long checksum2=1;
   unsigned long checksum3=2041;

      SetWindowText(hwndCode,"Name must be at least least 5 char(s)");

That's all folks, I hope you enjoyed this tutorial as much as I enjoyed writing it :-).


Return to Key Generators

© 1999, 2000 Hosted by CrackZ, authored by zoltan. 5th January 2000.