Introduction

Tutorial :- An interesting self-checking serial protection.
Target : Auto-IP Publisher v2.32 (http://www.autoip.net)
Tools :- SoftICE, W32Dasm 8.9x, HIEW.
Date :- 10th June 2000 (Updated on 12th June 2000).

Description :- There are not many tutorials on self-checking serials yet and I am proud to introduce you (well, sort of) to this self-checking serial protection. By the way, I found that this type of protection is more secure than some name/serial protections with *real* easy serial calculation algorithms (at least, for newbies).

Protection :- Self-checking serial.

Contact me at mankind001@bigfoot.com


The process

First of all, just in case you don't understand what a self-checking serial # protection is, I will explain it here. A self-checking serial is where the serial itself is validated by specific criteria. I have included the following source code (with comments) as an example :-

Program example ; {program's name}
uses crt ; {crt is a unit, this is similar to the include directive in c/c++}

var {variable declaration}
  serial : string ;
  serial2, serial3, i : integer ;

begin
  clrscr ; {clear the screen}
  write('Serial: ') ; {get user's input}
  readln(serial) ; {get input into string variable serial}
  i := 1 ; {assign value one to i}
  If length(serial) = 3 then {verify validity of serial only if length is 3}
    begin
      serial2 := Ord(serial[i]) + Ord(serial[i+2]) ; {sum 1st & 3rd bytes, store in serial2}
      serial3 := Ord(serial[i+1]) ; {get the ascii value of second byte and store in serial3}
    end ;
  If length(serial) <> 3 then {if length of serial not equal to 3, display bad_boy_message}
    begin
      writeln ; {print a blank line}
      writeln('Invalid serial!') ; {bad_boy_message}
    end ;

  If serial2 = serial3 then {if serial2 equal to serial3, display good_boy_message} 
    begin
    writeln('Valid serial') ;
  end ;
  If serial2 <> serial3 then {if serial2 not equal to serial3, display bad_boy_message}
    begin
      writeln('Invalid serial') ;
    end ;

end.

How to determine a valid serial. Note that the ASCII value of the second byte (character) must be equal to the ASCII of the first byte + the third byte. As an example, to calculate a valid second byte I choose 1 and 1 for the 1st & 3rd (each with decimal value of 49) and the second byte should be a 'b' i.e. 49 + 49 = 98 (ASCII decimal value of b). The full valid serial would be 1b1. Obviously there is more than one valid serial number. One thing you should pay attention to is the serial length check (it is the very first rule that your serial has to comply with, and this is often the case in protection schemes).

I would like to show you one example of such a protection (of course, it is not as easy as what I demonstrated above). Our target is Auto-IP Publisher 2.32. Start it, go to the registration screen and enter a fake registration code. Go into SoftICE, set a breakpoint on hmemcpy so that we would break immediately after our serial has been read (bpx hmemcpy). Leave SoftICE, press the OK button, you will break in SoftICE, press F11 once and F12 until you reach useful code (in this case 11 presses of F12 are required) :-

:0049FC13 MOV EDX, D, [EBP-04] <-- get serial.
:0049FC16 LEA EAX, D, [EBX+0000051C]
:0049FC1C CALL 00403BD0
:0049FC21 MOV EDX, D, [EBX+0000051C] <-- get serial again.
:0049FC27 MOV EAX, EBX
:0049FC29 CALL 0049F900 <-- main serial verification routine.
:0049FC2E TEST AL, AL <-- if al=0=bad_boy else al=1=good_boy.
:0049FC30 JE 0049FCA3 <-- bad_boy_jump.
:0049FC32 PUSH 00000000

* Possible StringData Ref from Code Obj ->"Registered!"

:0049FC34 MOV ECX, 0049FCEC

* Possible StringData Ref from Code Obj ->"You are now registered. Please restart Auto-IP"

:0049FC39 MOV EDX, 0049FCF8
:0049FC3E MOV EAX, D, [004A4EA8]
:0049FC43 MOV EAX, D, [EAX]
:0049FC45 CALL 004504EC

Here we have to step into the call instruction at address 0049FC29 (call 0049F900) to defeat this protection (don't even think of patching the JE at address 0049FC30 to JNE, that's lame and it won't work). Step into the call :-

:0049F900 PUSH EBP
:0049F901 MOV EBP, ESP
:0049F903 MOV ECX, 6

* Possible StringData Ref from Code Obj ->"110391481A28"

:0049F92F MOV EDX, 0049FB74 <-- above StringRef.
:0049F934 CALL 00403F0C <-- check if serial = StringRef.
:0049F939 JE 0049F94A <-- jump to bad_boy if yes.
:0049F93B MOV EAX, D, [EBP-04] <-- move serial to eax.

* Possible StringData Ref from Code Obj ->"J00811611@06"

:0049F93E MOV EDX, 0049FB8C <-- get next StringRef.
:0049F943 CALL 00403F0C <-- check if serial = StringRef.
:0049F948 JNE 0049F961 <-- jump to good_boy if not.

So, it checks if our serial is equal to either 110391481A28 or J00811611@06 and we will immediately be thrown to bad_boy if the serial is equal to one of them. My guess is that these two set of numbers are pirated serial numbers that have been blacklisted by the author. At first, I was fooled into believing that one of those two numbers was actually valid because the check really looks like one of those lame single serial check protections which we have all cracked in the past. Continue with the good_boy code :-

:0049F961 CMP D, [EBP-04], 0 <-- check if a serial was entered.
:0049F965 JE 0049FB36 <-- jump to bad_boy no serial was entered.
:0049F96B MOV EAX, D, [EBP-04]
:0049F96E CALL 00403DFC
:0049F973 CMP EAX, 0C <-- check length of serial equals 12 decimal.
:0049F976 JNE 0049FB36 <-- jump to bad_boy if not.

If you not using a 12-digit serial you won't be able to continue past here, so, set a breakpoint on address 0049F976 (disable all others), leave SoftICE and re-register with the example serial (say 231999812222) so that we can pass the length check. Press the OK button and you will be here after you jump from address 0049F976 :-

:0049F97F XOR EBX, EBX
:0049F981 MOV BL, B, [EAX] <-- get first byte.
:0049F983 SUB EBX, 6 <-- minus 6.
:0049F986 CMP EBX, 3B <-- compare whether it equals 59 decimal.
:0049F989 JL 0049F990 <-- jump to bad_boy if its lower.
:0049F98B CMP EBX, 54 <-- compare with 84 decimal (ascii of 'T').
:0049F98E JLE 0049F9A1 <-- jump if its lower than or equal to 84 decimal.

Here it makes sure that the first character of the serial is higher than 59 decimal but not more than 90 decimal (this actually means that capitals A-Z are accepted). Leave SoftICE again (of course, set a breakpoint on address 0049F98E and disable all other breakpoints first), modify your serial into the following (M23199981222) and re-register again. You will successfully jump here :-

:0049F9A1 LEA EAX, D, [EBP-0C]
:0049F9A4 MOV EDX, D, [EBP-04] <-- get serial.
:0049F9A7 MOV DL, B, [EDX+06] <-- get 7th byte of serial.
:0049F9AA MOV B, [EAX+01], DL
:0049F9AD MOV B, [EAX], 01
:0049F9B0 LEA EDX, D, [EBP-0C]
:0049F9B3 LEA EAX, D, [EBP-10]
:0049F9B6 CALL 00402A14
:0049F9BB LEA EAX, D, [EBP-14]
:0049F9BE MOV EDX, D, [EBP-04]
:0049F9C1 MOV DL, B, [EDX+03] <-- get 4rth of serial.
:0049F9CA LEA EDX, D, [EBP-14]
:0049F9CD LEA EAX, D, [EBP-10]
:0049F9D0 MOV CL, 2
:0049F9D2 CALL 004029E4
:0049F9D7 LEA EDX, D, [EBP-10]
:0049F9DA LEA EAX, D, [EBP-08]
:0049F9DD CALL 00403DA0
:0049F9E2 MOV EAX, D, [EBP-08]
:0049F9E5 CALL 004089F4
:0049F9EA CMP EBX, EAX <-- compare ebx with eax.

* ebx=ascii of first byte - 6, eax=integer form of 7th and 4th characters (where 7th is 9 and 4th is 1 will make a value of 91).

:0049F9EC JE 0049F9F5 <-- jump to good_boy if equals.
:0049F9EE XOR EBX, EBX

This piece of code retrieves the char of the 7th and 4th byte of the serial and makes them into a string (9 & 1 = 91, some sort of string concatenation) and turn that string into integer value ("91" into 91), then it compares the value of the ascii of the first byte - 6 (M=77 decimal, 77 - 6 = 71 decimal) with the combined value of 7th and 4th byte of the serial. To pass this check, modify your serial into the following : M23199781222. After passing the check you will jump to the following code :-

:0049F9F5 LEA EAX, D, [EBP-0C]
:0049F9F8 MOV EDX, D, [EBP-04]
:0049F9FB MOV DL, B, [EDX+02] <-- get 3rd byte of serial.
:0049FA15 MOV DL, B, [EDX+01] <-- get 2nd byte of serial.
:0049FA3E ADD EAX, 40 <-- add 64 decimal to eax.
:0049FA41 MOV EDX, D, [EBP-04]
:0049FA44 MOVZX EDX, B, [EDX+09] <-- get 10th byte of serial.
:0049FA48 CMP EAX, EDX <-- compare eax and edx.
:0049FA4A JE 0049FA53 <-- jump to good_boy if equal.

This is almost the same as the previous check, edx=ascii of 10th byte of serial while eax=integer form of char of 3rd and 2nd byte of serial ("3" + "1" = 31), so, to pass this check, modify your serial into the following again (M11199781K22). Note that I changed the 10th byte of serial into K-ascii 75 decimal ('K' is one of the characters of my handle!) and the 2nd and 3rd byte into 5 and 7 (making up 75, third byte is read first). Another thing to note is that 64 decimal will be added to the integer form of the char at the 3rd and 2nd byte of serial (that explains why the 3rd and 2nd byte of the serial should be 11 and not 57). Let's continue with the next check which is also the last :-

:0049FA53 LEA EAX, D, [EBP-1C]
:0049FA56 MOV EDX, D, [EBP-04]
:0049FA59 MOV DL, B, [EDX+01] <-- get the 2nd byte of serial.
:0049FA5C CALL 00403D24
:0049FA61 MOV EAX, D, [EBP-1C]
:0049FA64 CALL 004089F4
:0049FA69 MOV EBX, EAX
:0049FA6B LEA EAX, D, [EBP-20]
:0049FA6E MOV EDX, D, [EBP-04]
:0049FA71 MOV DL, B, [EDX+02] <-- get the 3rd byte of serial.
:0049FA74 CALL 00403D24
:0049FA79 MOV EAX, D, [EBP-20]
:0049FA7C CALL 004089F4
:0049FA81 ADD EBX, EAX
:0049FA83 LEA EAX, D, [EBP-24]
:0049FA86 MOV EDX, D, [EBP-04]
:0049FA89 MOV DL, B, [EDX+03] <-- get the 4th byte of serial.
:0049FA8C CALL 00403D24
:0049FA91 MOV EAX, D, [EBP-24]
:0049FA94 CALL 004089F4
:0049FA99 ADD EBX, EAX
:0049FA9B LEA EAX, D, [EBP-28]
:0049FA9E MOV EDX, D, [EBP-04]
:0049FAA1 MOV DL, B, [EDX+04] <-- get the 5th byte of serial.
:0049FAA4 CALL 00403D24
:0049FAA9 MOV EAX, D, [EBP-28]
:0049FAAC CALL 004089F4
:0049FAB1 ADD EBX, EAX
:0049FAB3 LEA EAX, D, [EBP-2C]
:0049FAB6 MOV EDX, D, [EBP-04]
:0049FAB9 MOV DL, B, [EDX+05] <-- get the 6th byte of serial.
:0049FABC CALL 00403D24
:0049FAC1 MOV EAX, D, [EBP-2C]
:0049FAC4 CALL 004089F4
:0049FAC9 ADD EBX, EAX
:0049FACB LEA EAX, D, [EBP-30]
:0049FACE MOV EDX, D, [EBP-04]
:0049FAD1 MOV DL, B, [EDX+06] <-- get the 7th byte of serial.
:0049FAD4 CALL 00403D24
:0049FAD9 MOV EAX, D, [EBP-30]
:0049FADC CALL 004089F4
:0049FAE1 ADD EBX, EAX
:0049FAE3 LEA EAX, D, [EBP-0C]
:0049FAE6 MOV EDX, D, [EBP-04]
:0049FAE9 MOV DL, B, [EDX+08] <-- get the 9th byte of serial.
:0049FAEC MOV B, [EAX+01], DL
:0049FAEF MOV B, [EAX], 01
:0049FAF2 LEA EDX, D, [EBP-0C]
:0049FAF5 LEA EAX, D, [EBP-10]
:0049FAF8 CALL 00402A14
:0049FAFD LEA EAX, D, [EBP-14]
:0049FB00 MOV EDX, D, [EBP-04]
:0049FB03 MOV DL, B, [EDX+0B] <-- get the 12th byte of serial.
:0049FB06 MOV B, [EAX+01], DL
:0049FB09 MOV B, [EAX], 01
:0049FB0C LEA EDX, D, [EBP-14]
:0049FB0F LEA EAX, D, [EBP-10]
:0049FB12 MOV CL, 02
:0049FB14 CALL 004029E4
:0049FB19 LEA EDX, D, [EBP-10]
:0049FB1C LEA EAX, D, [EBP-34]
:0049FB1F CALL 00403DA0
:0049FB24 MOV EAX, D, [EBP-34]
:0049FB27 CALL 004089F4
:0049FB2C CMP EBX, EAX <-- compare.
:0049FB2E JE 0049FB34 <-- jump to good_boy if equal.
:0049FB30 XOR EBX, EBX
:0049FB32 JMP 0049FB36 <-- jump to ret with bad_boy value.
:0049FB34 MOV BL, 01 <-- set good_boy value.
:0049FB6B RET

Lots of code. It adds up all the values of the 2nd, 3rd, 4th, 5th, 6th and 7th bytes of the serial and stores it in ebx (those bytes are used in char form that we use in our normal life : 1 + 1 + 1 + 9 +9 + 7 = 28 decimal), for eax it does string concatenation and conversion into integer form of the 9th and the 12th byte of serial ("1" + "2" = "12" = 12 decimal) and then it compares both of them. To pass this final check, just simply replace the 9th byte of serial with 2 and the 12th byte of serial into 8. The final valid serial will be M11199782K28.

Try it and you will be thanked for registering. There are some other minor checks like for example the 7th and the 4th byte of serial must be integer value (0-9) which I didn't mention. Here's the explanation of its protection in a nutshell :-

1st char = CharOf(IntegerOf(StringOf(7th char)+StringOf(4th char)) + 6), only A-Z characters are valid.
2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 11th chars = Anything but 7th and 4th chars must be integer numbers(0-9) and there might still be some other checks which you must find out yourself.
9th char = FirstCharOf(StringOf(IntegerOf(2nd char + 3rd char + 4th char + 5th char + 6th char + 7th char))).
10th char = CharOf((IntegerOf(StringOf(3rd char) + StringOf(2nd char))) + 64).
12th char = SecondCharOf(StringOf(IntegerOf(2nd char + 3rd char + 4th char + 5th char + 6th char + 7th char))).

IntegerOf = the integer value(not ascii value) of something.
StringOf = the value of an integer(not ascii value) treated as string.
FirstCharOf = first character of a string.
CharOf = the ascii character of a number.
SecondCharOf = second character of a string.

By the way, did you take note of something which I asked you to at the beginning of the tutorial? It is this line which is the beginning of the serial verification call :-

* Referenced by a CALL at Addresses:
|:0049AF69 , :0049FC29

The serial verification call referenced in 2 places. One should be when we register and another should be during the startup of the program. Its also possible for you to patch this call to crack this protection. How to patch? Since this call is important for returning al's value which should be either 0 or 1, you should use HIEW to load the executable, go to the beginning of the call and insert the following instruction (F3, then F2) :-

MOV AL, 1 <-- opcode B001
RET <-- opcode C3

Press F9 to save and F10 to exit HIEW. Now, try to load up that patched executable. Its cracked. I should have ended this tutorial now, but as a reverser, I think I could do more than this. Probably a keygen or brute-forcer. Thanks a lot to ^tCM^'s explanation, help and encouragement, below is a source of a keygen/brute-forcer for Auto-IP Publisher 2.32 coded in Turbo Pascal 7.0 by me. It should also compile on any other compatible Pascal compilers. Sorry, this time I didn't comment my code (I'm running out of time) but if you are really interested in it, I will comment it for you anytime you wan't (just mail and ask me, ok?).

Program keygen ;
uses crt ;
var
 temp1, temp2, char1, char10, temp3, temp4, char9, char11, char8, char12, temp7, temp5, temp6, 

fixed : string ;
 tempo1, tempo2, i, tempo3, tempo4, tempo5, tempo6, tempo7, tempo8, tempo9, tempo10, 
 tempo11 : longint ; e, random1, random2 : integer ;
 tempfile : text ;

Begin
 clrscr ;
 Writeln('++++++++++++++++++++++++++++++++++++++++++++++++++') ;
 Writeln('+ KeyGen/Brute-Forcer for Auto-IP Publisher 2.32 +') ;
 Writeln('+ by ManKind on 11th June 2000                   +') ;
 Writeln('+ Service for Mankind                            +') ;
 Writeln('+ mankind001@bigfoot.com                         +') ;
 Writeln('++++++++++++++++++++++++++++++++++++++++++++++++++') ;
 assign(tempfile, 'serials.txt') ;
 rewrite(tempfile) ;
 close(tempfile) ;
 Writeln('Please be patient. Saving serials...') ;
 for i := 1 to 150 do
  begin
   randomize ;
   tempo1 := 1000+random(9999-1000);
   tempo1 := tempo1 * 98 ;
   Str(tempo1, temp1) ;
   temp2 := Chr(Ord(temp1[6])) + Chr(Ord(temp1[3])) ;
   Val(temp2, tempo2, e) ;
   char1 := Chr(tempo2+6) ;
   temp3 := Chr(Ord(temp1[2])) + Chr(Ord(temp1[1])) ;
   Val(temp3, tempo3, e) ;
   char10 := Chr(tempo3+64) ;
   Val(temp1[1], tempo5, e) ;
   Val(temp1[2], tempo6, e) ;
   Val(temp1[3], tempo7, e) ;
   Val(temp1[4], tempo8, e) ;
   Val(temp1[5], tempo9, e) ;
   Val(temp1[6], tempo10, e) ;
   tempo4 := tempo5 + tempo6 + tempo7 + tempo8 + tempo9 + tempo10 ;
   Str(tempo4, temp5) ;
   char9 := Chr(Ord(temp5[1])) ;
   char12 := Chr(Ord(temp5[2])) ;
   fixed := 'qOH0Uwke8ZNC61nGM4tS5BAmRLlopr9abcWPJKxy7FIhguvTDLXjsXY2V3dfizQ' ;
   random1 := random(63) + 1 ;
   random2 := random(63) + 1 ;
   char8 := Chr(Ord(fixed[random1])) ;
   randomize ;
   delay(150) ;
   char11 := Chr(Ord(fixed[random2]))  ;
   temp7 := char1 + temp1 + char8 + char9 + char10 + char11 + char12 ;
   tempo11 := Ord(temp7[1]) ;
   If (Length(temp7) = 12) then
     If (tempo11 > 59) and (tempo11 < 91) then
      begin
       append(tempfile) ;
       writeln(tempfile, temp7) ;
       close(tempfile) ;
       delay(150) ;
      end;
  end ;
 Writeln('All serials have been saved to serials.txt file. Press enter to exit to system') ;
 readln ;
end.

This program should at least (and I mean AT LEAST) generate a few hundred valid serial #'s for Auto-IP Publisher 2.32. It will add new serials to the serials.txt file everytime it is run. I also noticed that it is quite slow, but it does its work, so, just be patient for about 1-2 minutes and do something else. Hope to see you soon on my next tutorial. As usual, contact me if I make any mistake, give me your feedback, comments, suggestions and opinions about this tutorial and my way of presenting it.

Extra notes

I am quite pleased finishing this tutorial. I will not write anymore newbie tutorials in the future, instead only interesting protections because I need more time (as if 24 hours a day is not enough) to concentrate on my real-life, my family, my education, my friends, my happiness, my love, etc. which I have just found out IS the MOST IMPORTANT thing of one's life... That's all for now.


Ending

Thanks and greetz to :-

+ORC, +HCU, Sandman, HarvestR, tKC, ytc_, Punisher, Kwai_Lo, TORN@DO, CrackZ, cLUSTER, LaZaRuS, mISTER fANATIC, yes123, WhizKiD, Volatility, ACiD BuRN, Eternal Bliss, R!SC, Kwazy Webbit, +Mammon, MisterE, Shadow, ^tCM^, WaJ, Borna Janes, Kathras, tsehp, AB4DS(Death), douby, Steinowitz, Lord Soth, Latigo, Lucifer48, NeuRaL_NoiSE, Fravia+, [dshadow], Latigo, Duelist, Alpine, flag eRRatum, Nitrus, +Frog's Print, Muad`Dib, Acid_Cool_178, Iczelion, Razzia, Warezpup, Bomber Monkey, llama and other crackers, individuals and organisations who have helped me, either directly or indirectly.

Service for Mankind
ManKind
mankind001@bigfoot.com