Basic Tutorials on Key Generation and Key File Routines

Compiled by Dr. ME!

These tutorials cover fundamentals in writing key generation and key file routines. A linked assembly language reference (Drme2.htm) is included so the students can follow the code more conveniently. Both files: Drme1.htm and Drme2.htm must be present in the same directory. The purpose of this compilation is not to crack software; but to enhance knowledge. There is absolutely no crack for any software in these tutorials; only code snippets to highlight points under discussion. -- Dr. ME!

Converting Strings to Uppercase

I'll explain this routine so you can recognize it as soon as you meet it again. It is a common procedure for converting strings to uppercase. This is part of the call.
:00406219 8A02                    mov al, byte ptr [edx] ; byte of string
:0040621B 3C61                    cmp al, 61      ; is it already upcase?
:0040621D 7206                    jb 00406225     ; if so then jump _____
:0040621F 3C7A                    cmp al, 7A      ; higher than z?       |
:00406221 7702                    ja 00406225     ; if so then jump -----|
:00406223 2C20                    sub al, 20      ; convert to lower case|                                                                                                |
* Referenced by a (C)onditional Jump at Addresses: ---------------------- 
|:0040621D(C), :00406221(C)

:00406225 8806                    mov byte ptr [esi], al
:00406227 42                      inc edx  ; increase edx to get next char
:00406228 46                      inc esi  ; 
:00406229 4B                      dec ebx  ; decrease string length counter
:0040622A 85DB                    test ebx, ebx ; reach end of string ?
:0040622C 75EB                    jnz 00406219  ; if not, jump back for more

This tests each character to see if it is lower than 'a,' in which case it is already an upper case letter or a symbol, so it jumps to get the next letter. If it is not, then it tests the upper limit to see if it is between the two numbers, 61h and 7Ah. If so, it is a lower case letter and subtracting 20h from it will convert it to an upper case letter. (Note that uppercase letters from A to Z are in 41h to 58h and lowercase letters from a to z are in 61h to 7Ah range.) I just explain this here because MANY programs use this type of routine to convert to uppercase and I know some newbies mistaking such routines with part of the main protection routine.

Another Way to Convert to Uppercase

Bit#             76543210             Bit#       76543210
A                01000001             Z          01011010
a                01100001             z          01111010
So you see that the only difference between an uppercase and its lowercase letter is in bit# 5. If this bit is 0, we will have an uppercase; if 1 a lowercase letter. The following code snippet will also change lowercase to uppercase:
MAIN   proc near
       lea bx, title+1
       mov cx,31

       mov ah,[bx]
       cmp ah,61h
       jb B30
       cmp ah,7Ah
       ja B30
       and ah,1101 1111b  ;AND changes bit #5 from 1 to 0
       mov [bx],ah

       inc 13x
       loop B20
MAIN   endp

Calculating Length of the String Entered

Recognizing the Elements of a Counter

xxxx:00406587    mov ecx, FFFFFFFF 

Note: anytime you see FFFFFFFF being put into ecx, you are most likely at the start 
of a routine that determines the length of some string or number. 

xxxx:0040658C    sub eax, eax
xxxx:0040658E    repnz         ;while not 0, scan string byte 
xxxx:0040658F    scasb         ;classic string length comparison and calculation 
xxxx:00406590    not ecx       ;ecx = length of string + 1 
xxxx:00406592    dec ecx       ;actual length in ecx
xxxx:00406593    je 004066A4   ;If ecx=0 (no name entered) then jump to the line 
                                4066A4, if not, continue:

This code will calculate the number of characters we entered as name. This number 
will be stored in ecx.

Dealing with "-" and Internal Lookup Tables (1)

Now, let's see what is contained inside that call... A good way to start is to remember what value you want and look at the END of the procedure (before the RET command) the different ways you can return from that call. Look here: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00482358(C) | :00482361 33C0 xor eax, eax --> BAD_FLAG! * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:004822EA(U), :004822FF(U), :0048234D(U), :0048235F(U) | :00482363 5F pop edi :00482364 5E pop esi :00482365 5B pop ebx :00482366 5D pop ebp :00482367 C3 ret (In fact after each suspect call, we must check on eax to see if it is zero or not. Thus we can say if the call sets the eax that is used in the subsequent test. This way, we can say whether the call does the compare at least. Maybe it does the calculation too.) Here it is: we know that eax=0 is the bad flag, so we can find in which places there is a bad or good choice... Another important thing is that 482363 address called FOUR times (all with UNCONDITIONAL jumps!) and that 482358 called one time with a conditional jump: does this mean there's only ONE way to make the wrong choice? Of course... NO!!! Look at other pieces of code like this: :004822E8 33C0 xor eax, eax --> EAX=0 BAD FLAG! :004822EA EB77 jmp 00482363 --> END_PROC Here the bad flag is created before the jump to the end sequence... and you can understand that xor eax,eax isn't anything else than a CREATE_BAD_FLAG only from that jump! There are many xors in a program... and not all of them do the same thing of course: this is the way you can understand which ones seem interesting for you! Now... another suggestion: COMMENT the disassembled file! If you do it, in a few minutes it will be like reading an Aesop's fable: you'll understand every little thing immediately! First of all, let's call that 482363 address END_PROC, so you can see in the code when you exit the call... and you are also able to call the xor eax, eax CREATE_BAD_FLAG or with a similar name. Use your fantasy, call that "YOU_NOT_REGGED" or whatever you like, the important is that you understand it :) Also, follow the addresses of the parameters passed to the call, in this case the PW and the NAME: since you've passed the name AND THEN the pw, you'll have the first one in ebp+08, and the second one in ebp+0C, as you can see from the code: :004822D6 8B750C mov esi, dword ptr [ebp+0C] --> PW :004822D9 8B5D08 mov ebx, dword ptr [ebp+08] --> NAME :004822DC 53 push ebx :004822DD E87E1D0200 call 004A4060 :004822E2 59 pop ecx :004822E3 83F805 cmp eax, 00000005 :004822E6 7304 jnb 004822EC :004822E8 33C0 xor eax, eax --> EAX=0 BAD FLAG! :004822EA EB77 jmp 00482363 --> END_PROC Now you can follow it quite easily: put pw address in esi, put name address in ebx, do something with the name (you see there's that PUSH EBX, don't you?), then check that the returned value is at least 5. If eax is less than 5 return an error flag. You want me to comment it? So you're lazier than me! :) :004822D6 8B750C mov esi, dword ptr [ebp+0C] --> PW :004822D9 8B5D08 mov ebx, dword ptr [ebp+08] --> NAME :004822DC 53 push ebx :004822DD E87E1D0200 call 004A4060 --> CHECK LENGTH :004822E2 59 pop ecx :004822E3 83F805 cmp eax, 00000005 --> Name must be at least 5 chars long :004822E6 7304 jnb 004822EC :004822E8 33C0 xor eax, eax --> EAX=0 BAD FLAG! :004822EA EB77 jmp 00482363 --> END_PROC Ta daaa... (I correct myself: TA-CHAAAAN! ;)) I bet you haven't guessed it! :) As you can see, it's just a matter of comments and all becomes easily readable! Learn this technique and your cracking will become more effective, also you will rest your eyes on white paper (or whatever color you like) instead of a 14'' monitor at a 1280x1024 resolution Now let's see what else this call does with our beloved parameters... * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004822E6(C) (the password is >=5 char) | :004822EC 56 push esi --> again PW :004822ED 53 push ebx --> again Name :004822EE E8FDFEFFFF call 004821F0 --> do something with them :004822F3 83C408 add esp, 00000008 :004822F6 85C0 test eax, eax :004822F8 7407 je 00482301 --> IF eax=0 THEN do some other things.. :004822FA B801000000 mov eax, 00000001 --> GOOD_FLAG! :004822FF EB62 jmp 00482363 --> END_PROC Hey!!! We have reached a VERY interesting point! You see that test eax, eax and the jump after that? Well, we DON'T WANT to make it jump, so we will have our GOOD_FLAG and we will register immediately! So from a "long" procedure like this (well if you call this long you haven't seen some M$ calls :-|) you can cut away more than half of it and start to consider just that "call 4821f0". ************** CALL 4821f0: ************** First of all, remember that we want eax DIFFERENT FROM 0. So, every time we find something like xor eax,eax and then a jump to the end of the call we know the procedure is setting a BAD flag. So, first of all go and give a look to the end of the call: is there a xor in that piece of code? Is there a conditional jump near the end? And so on...Here it is: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004822BB(C) | :004822C1 B801000000 mov eax, 00000001 --> GOOD_FLAG! * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:0048220F(U), :0048222C(U), :0048227F(U), :004822BF(U) | :004822C6 5F pop edi :004822C7 5E pop esi :004822C8 5B pop ebx :004822C9 8BE5 mov esp, ebp :004822CB 5D pop ebp :004822CC C3 ret LOOK_AND_UNDERSTAND_!!! It's really plain text! I correct myself, it's easier than plain text! It's like an illustrated book for children! We have FOUR UNCONDITIONAL jumps to the end of the procedure, please take a look at them: ***JUMP 1:*** :0048220D 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! :0048220F E9B2000000 jmp 004822C6 --> END_PROC ***JUMP 2:*** :0048222A 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! :0048222C E995000000 jmp 004822C6 --> END_PROC ***JUMP 3:*** :0048227D 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! :0048227F EB45 jmp 004822C6 --> END_PROC ***JUMP 4:*** :004822BD 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! :004822BF EB05 jmp 004822C6 --> END_PROC They are _absolutely_identical_!!! So now you know that whenever there is a jump to END_PROC, it's because something went wrong (in fact, every time there is an xor before the jump). Otherwise, if you come to that instruction from the right address (4822C1), it's because the jump at 4822BB took you there.(Very typical is the "Call ... test eax ... jne XXXX" section. This is a very common protection scheme. But more important is, that the Jump decides if you go the BAD_GUY Message box or not.) It therefore is the deciding jump. But let's go straight on with the call and let's see what happens: :004821F0 some junk here... :004821F9 8B750C mov esi, dword ptr [ebp+0C] --> PW :004821FC 6A2D push 0000002D You know what 2D is? NO? NOOO??? Well I pardon you only because you're newbies, but now you have to learn it by heart and if I find you forgot it I'll show no mercy! 2D is the hex value of the '-' char, which is often used as a separator inside the passwords. Sometimes the '-' char is not used just as a separator, but is part of the pw itself: if it's not found or if it's in the wrong position, the password is wrong. In this case, you don't have to put the '-' in a particular place, but you do have to put it somewhere inside the pw: in fact, it is used as a separator to check two different parts of the pw in two different ways. Look here: :004821FE 56 push esi --> PW :004821FF E8081E0200 call 004A400C --> Finds the "-" and returns its position :00482204 83C408 add esp, 00000008 :00482207 8BD8 mov ebx, eax :00482209 85DB test ebx, ebx :0048220B 7507 jne 00482214 :0048220D 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! (no "-") :0048220F E9B2000000 jmp 004822C6 --> END_PROC This is the piece of code which checks for the presence of the 2D value inside the password. If there isn't any '-' (or if the char is the first char of the password) the program exits the call with 0 value in eax, which is, as you know, a BAD flag. Now here's what happens if 2D value is present: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0048220B(C) (the password has a "-" inside it) | :00482214 C60300 mov byte ptr [ebx], 00 --> zeros the "-" :00482217 56 push esi --> PW :00482218 E89F710200 call 004A93BC --> Creates first checksum: IMPORTANT!!! :0048221D 59 pop ecx --> PW :0048221E 8945FC mov dword ptr [ebp-04], eax --> SAVES FIRST CHECKSUM! The first line of code zeroes the '-' symbol. This often happens during serial checks, because most of the times 2D values are not to be included in the algorithm which builds the password from the name or (as in this case) builds a checksum from the password and compares it with another checksum built from the name. That CALL 004A93BC is nothing else than a "_atol" function, which returns a number from a string which represents a number: for instance, the string "12345" returns the value 12345, which is a NUMBER and not a sequence of chars. After the checksum is created in eax, its value is saved at the location pointed by [ebp-04]. :00482221 C6032D mov byte ptr [ebx], 2D --> puts the "-" again :00482224 43 inc ebx :00482225 803B00 cmp byte ptr [ebx], 00 --> must have something after the "-" :00482228 7507 jne 00482231 :0048222A 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! :0048222C E995000000 jmp 004822C6 --> END_PROC Now there's another check: the 2D character must be INSIDE the password, not at its end. From this we can understand that '-' is just a separator that splits the password in two pieces, which generate two different checksums (and we can suppose that the password will be used to make two values too). * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00482228(C) (the "-" is NOT the last character!) | :00482231 53 push ebx --> second section of the password :00482232 E885710200 call 004A93BC --> Creates second checksum: IMPORTANT!!! :00482237 59 pop ecx --> ecx may have been used for counting!!! :00482238 8945F8 mov dword ptr [ebp-08], eax --> SAVES SECOND CHECKSUM Here, with the same method as in the piece of code above, the second checksum is created and then it is saved in the address pointed by [ebp-08]. Now the beautiful part of the algorithm starts... first it checks for the length of the name, and lets you go on only if your name is at least 3 chars long: :0048223B 8B4508 mov eax, dword ptr [ebp+08] :0048223E 50 push eax --> Name :0048223F E81C1E0200 call 004A4060 --> Strlen :00482244 59 pop ecx --> was ecx used for counting? :00482245 8945F4 mov dword ptr [ebp-0C], eax --> Saves name's length :00482248 33C0 xor eax, eax :0048224A 33DB xor ebx, ebx :0048224C BA03000000 mov edx, 00000003 :00482251 8B4D08 mov ecx, dword ptr [ebp+08] --> name's address :00482254 83C103 add ecx, 00000003 :00482257 3B55F4 cmp edx, dword ptr [ebp-0C] --> Cmp (3, namelength) :0048225A 7D1C jge 00482278 Now here is the Check-the-table piece of code: remember, you don't have to clone the assembly routine, you have to UNDERSTAND what's going on and then try to replicate it... maybe in an easier way! :0048225C 0FB631 movzx esi, byte ptr [ecx] --> put in esi the 4th letter of the name :0048225F 0FAF348544BB4B00 imul esi, dword ptr [4*eax + 004BBB44] --> CHECK THE TABLE! ;esi=esi*table value :00482267 03DE add ebx, esi --> ebx=ebx+esi :00482269 40 inc eax --> eax=0, 1, 2, 3... :0048226A 83F826 cmp eax, 00000026 :0048226D 7E02 jle 00482271 :0048226F 33C0 xor eax, eax --> translated: "if eax>26 then eax=0" * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0048226D(C) | :00482271 42 inc edx --> edx=4, 5, 6... :00482272 41 inc ecx --> ecx=name's address+4, 5, 6... :00482273 3B55F4 cmp edx, dword ptr [ebp-0C] --> cmp edx, namelength :00482276 7CE4 jl 0048225C --> continue while edx is lower than namelength At the end of this procedure ebx contains a "magic value" that will be later compared with the first checksum. How does this algorithm work? Well, this is a loop that depends on the length of the string, as you can see at address 482276: the algo starts from position 4 of the name string and then does something with every character of the name. What does it do? Let's see... first, it puts the fourth (and then the fifth, and then the sixth...) letter of the name in esi, then it multiplies esi by a value that is taken from a table, whose address starts at 4bbb44. As you can see, the values are taken from address 4bbb44 with a "step" of 4 bytes each time (4*eax, and eax is increased by 1 every time), and they are double words in the format "000000xx". So, here is the table with just the last byte for every value: Start: 4bb44 | +00 +04 +08 +0C +10 +14 +18 +1c --------------+---------------------------------------- +20 | 0b 06 11 0c 0c 0e 05 0c | +40 | 10 0a 0b 06 0e 0e 04 0b | +60 | 06 0e 0e 04 0b 09 0c 0b | +80 | 0a 08 * 0a 0a 10 08 04 06 | +A0 | 0a 0c 10 08 0a 04 10 00 As you can see at address 48226A, the maximum value of eax can be 26, then it is zeroed again: so, the only values you should need are the ones before the asterisk. I wanted to put the other values too, because I thought it was strange that values SO similar to the ones we're interested in are not used... maybe in another version the same table will be used with a different maximum value for eax, and this will be enough to change some keys for particularly long names... who knows! After esi is multiplied with the value taken from the table, it is added to ebx, which is originally 0 and at the end of the loop contains the final "magic value". So, as you can see, the algorithm is not so hard to understand: for each letter from the fourth to the last, multiply its value by another value taken from the table, add all the values obtained and then compare the result with the first checksum, as you can see below. :00482278 3B5DFC cmp ebx, dword ptr [ebp-04] --> cmp ebx, first_checksum NOTE: ebx, which is built from the NAME, must be equal to the first checksum, which is built from the PASSWORD. This is the relation between the two strings you inserted. The second checksum is compared with another value, which is created with this algorithm: :00482295 0FB631 movzx esi, byte ptr [ecx] --> move 4th letter in esi :00482298 0FB679FF movzx edi, byte ptr [ecx-01] --> move 3rd letter in edi :0048229C 0FAFF7 imul esi, edi --> esi=esi*edi :0048229F 0FAF348544BB4B00 imul esi, dword ptr [4*eax + 004BBB44] --> esi=esi*table value :004822A7 03DE add ebx, esi --> ebx=ebx+esi ... :004822B8 3B5DF8 cmp ebx, dword ptr [ebp-08] --> cmp ebx, second_checksum :004822BB 7404 je 004822C1 --> JMP_GOOD_FLAG! Et voila'! This is the last part of the protection! The value is created in a different way: this time we load two letters (the 4th and the 3rd, then the 5th and the 4th, and so on), we multiply them together and then with the table value, finally we save the value obtained in ebx. At the end this is compared with the second checksum, and if this is ok too we jump to GOOD_FLAG and, then, the end of the procedure. Phew... the job is done.

Dealing with Internal Lookup Tables (2)

* Referenced by a CALL at Addresses: |:00406CC6 , :0040C89B , :0040CA4C , :0040CA58 ; called from a few places ! :0040DC20 8B442404 mov eax, dword ptr [esp+04] ; name :0040DC24 56 push esi ; push name :0040DC25 8B355CA84100 mov esi, dword ptr [0041A85C] ; esi = C69AA96C :0040DC2B 50 push eax ; push name :0040DC2C 81CE78030000 or esi, 00000378 ; or the starting number :0040DC32 E8B9DEFFFF call 0040BAF0 ; 1st call :0040DC37 83C404 add esp, 00000004 :0040DC3A 03F0 add esi, eax ; add the result to esi :0040DC3C 8B44240C mov eax, dword ptr [esp+0C] ; eax = company :0040DC40 50 push eax ; push company :0040DC41 E8AADEFFFF call 0040BAF0 ; 2nd call :0040DC46 83C404 add esp, 00000004 :0040DC49 03C6 add eax, esi ; add previous number to result of call :0040DC4B 5E pop esi ; restore esi :0040DC4C C3 ret ; go back for compare The program is performing the same manipulations on both our name and our company in the calls to 0040BAF0 and is adding the results to the starting number or'd with 378h. This code isn't hard to understand, lets see if the manipulations are more difficult. :0040BAF0 53 push ebx :0040BAF1 56 push esi :0040BAF2 8B74240C mov esi, dword ptr [esp+0C] ; store some things :0040BAF6 57 push edi :0040BAF7 55 push ebp :0040BAF8 33FF xor edi, edi :0040BAFA 56 push esi ; push input for lstrlen call * Reference To: KERNEL32.lstrlenA, Ord:029Ch :0040BAFB FF15C8A44200 Call dword ptr [0042A4C8] :0040BB01 85F6 test esi, esi ; did the sucker enter nothing ? :0040BB03 7432 je 0040BB37 ; then clear off :0040BB05 85C0 test eax, eax ; lstrlen return :0040BB07 742E je 0040BB37 ; clear off if 0 :0040BB09 B900000000 mov ecx, 00000000 :0040BB0E 7E27 jle 0040BB37 * Referenced by a (C)onditional Jump at Address: |:0040BB35(C) :0040BB10 0FBE9C0818BE4100 movsx ebx, byte ptr [eax+ecx+0041BE18] This is getting something from a memory location. Do a 'd 0041BE18' and you will see a long string. #serB&nz|mfM1/5(!sd$Mq.{s]+sFjtKpzSdtzoXqmb^Al@dv:s?x/ This is the first datatable, we call it DataTable1. Remember eax is the length of name and ecx is the counter of the loop. Therefore it is moving the [namelength+count] byte of the DataTable1 into ebx. :0040BB18 0FBE2C31 movsx ebp, byte ptr [ecx+esi] ; byte of name[count] :0040BB1C 8D5101 lea edx, dword ptr [ecx+01] ; counter increase :0040BB1F 0FAFDD imul ebx, ebp ; ebx = (NameByte*DataTable1Byte) :0040BB22 0FBE8950BE4100 movsx ecx, byte ptr [ecx+0041BE50] Here is the second datatable, DataTable2, do a 'd 0041BE50' and you will see : |b!pz*ls;rn|lf$vi^Axpe)rx5aic&9/2m5lsi4@0dmZw94cmqpfhw Ecx is the counter so it is the datatable[count] byte which is moved into ecx. :0040BB29 0FAFD9 imul ebx, ecx ; ebx * DataTable2[count] :0040BB2C 0FAFDA imul ebx, edx ; ebx * (count+1) :0040BB2F 03FB add edi, ebx ; store in edi :0040BB31 8BCA mov ecx, edx ; loopcount+1 :0040BB33 3BD0 cmp edx, eax ; end of input ? :0040BB35 7CD9 jl 0040BB10 ; go for more * Referenced by a (C)onditional Jump at Addresses: |:0040BB03(C), :0040BB07(C), :0040BB0E(C) :0040BB37 8BC7 mov eax, edi ; move final result into eax :0040BB39 5D pop ebp :0040BB3A 5F pop edi :0040BB3B 5E pop esi :0040BB3C 5B pop ebx :0040BB3D C3 ret This is done with both the name and the company which we entered and after the call returns, the total value of all the manipulations are stored in eax for the compare with our code (in decimal). SUMMARY OF PROTECTION : 1) Start the CodeStore at C69AA96Ch then OR it with 378h 2) Take byte from name and multiply it with byte(DataTable1[namelength+count]) 3) Multiply the result with byte(DataTable2[count]) 4) Multiply that result with loopcount+1 and store result 5) Repeat this for each letter of the name then add final summation to a variable 6) Repeat steps 2-5 for the company string.

The Study of a Registration Number

The thing we want to do is to have a close look at the call that deals with the Serial Number and understand what it does. Letīs take some time and think about what the call could do, what it has to do? First of all the call must compare the fake serial with the real serial. We just have to find out where and how. It then must store the result in eax in the end. The call might also check the style of the serial and the username. If it finds that the serial is styled the wrong way it stops. For example if a serial has to look like xxx-xx-xxx and it doesnīt it can quickly kick us out. Why would it be useful to do this? On the one hand it speeds up the process because it skips the calculating routine. This is marginal and of no real use for the user, who normally logs in only once. In fact it makes life harder for the bad boys, because itīs keeping us away from the real compare of the serials. If a bad boy doesnīt know this, he getīs caught in this style-check, trying to find the real serial which isnīt there. This could take much time to realize and make a brute force guy give in. Another thing we could find in the call is some fake compares or some fake calculations. We must be careful in our choice which way to go if we want to be sucessful. Something we should also care for is a conversion of our User Name. Itīs often upcased or downcaesd for example. (HaRt-->hart downcase) or some parts are changed or cut off or added. Weīll also look out for the calculating routine where the name is used to produce the real serial. These are the main things we could meet within the call. Well the programm is waiting for us. Return to the call at :00403541 E8DC9D0000 call 0040D322and step into it by pressing F8. Here we see something like this. * Referenced by a CALL at Addresses: |:00403541 , :0040D2DD | :0040D322 55 push ebp :0040D323 8BEC mov ebp, esp :0040D325 83EC30 sub esp, 00000030 :0040D328 8B450C mov eax, dword ptr [ebp+0C] :0040D32B 53 push ebx :0040D32C 56 push esi :0040D32D 57 push edi :0040D32E 85C0 test eax, eax :0040D330 0F840F010000 je 0040D445 ;jump if no Name entered (eax=0) :0040D336 8B5D08 mov ebx, dword ptr [ebp+08] :0040D339 85DB test ebx, ebx ;jump if no Serial entered (eax=0) :0040D33B 0F8404010000 je 0040D445 :0040D341 80384D cmp byte ptr [eax], 4D ;eax has the serial stored, the first char has to be an M (=2d hex) :0040D344 0F85FB000000 jne 0040D445 ;interesting that this and.... :0040D34A 80780157 cmp byte ptr [eax+01], 57 ;second one ([eax+1]) has to be ;a W :0040D34E 0F85F1000000 jne 0040D445 ; ...that jump go to the same place :0040D354 6A13 push 00000013 :0040D356 50 push eax :0040D357 8D45E4 lea eax, dword ptr [ebp-1C] :0040D35A 50 push eax :0040D35B E849A1FFFF call 004074A9 :0040D360 83C40C add esp, 0000000C :0040D363 8D45E4 lea eax, dword ptr [ebp-1C] :0040D366 6A2D push 0000002D :0040D368 50 push eax :0040D369 E8120D0000 call 0040E080 ;This Call checks the rest of the style. ;If it fails you wonīt get to :0040D36E 8BF0 mov esi, eax ;the serial compare. :0040D370 59 pop ecx :0040D371 85F6 test esi, esi :0040D373 59 pop ecx :0040D374 89750C mov dword ptr [ebp+0C], esi :0040D377 0F84C800000 je 0040D445 ;See we got 40D445 here too! :0040D37D 802600 and byte ptr [esi], 00 ;The "-" is deleted :0040D380 8D45F8 lea eax, dword ptr [ebp-08] * Possible StringData Ref from Data Obj ->" " | :0040D383 68F87A4100 push 00417AF8 :0040D388 50 push eax :0040D389 E832060000 call 0040D9C0 :0040D38E 59 pop ecx :0040D38F 8D45F8 lea eax, dword ptr [ebp-08] :0040D392 59 pop ecx :0040D393 6A04 push 00000004 :0040D395 53 push ebx :0040D396 50 push eax :0040D397 E834190000 call 0040ECD0 ;The first 4 letters from the Name ;are taken and written in memory :0040D39C 8A45E4 mov al, byte ptr [ebp-1C] :0040D39F 83C40C add esp, 0000000C :0040D3A2 84C0 test al, al :0040D3A4 7427 je 0040D3CD . . | :0040D41E 68F47A4100 push 00417AF4 :0040D423 8D45D0 lea eax, dword ptr [ebp-30] :0040D426 6A14 push 00000014 :0040D428 50 push eax :0040D429 E872040000 call 0040D8A0 ;generates real serial :0040D42E 83C410 add esp, 00000010 :0040D431 46 inc esi :0040D432 8D45D0 lea eax, dword ptr [ebp-30] :0040D435 56 push esi :0040D436 50 push eax :0040D437 E8740A0000 call 0040DEB0 ;compares serials. sets eax=1 if bad; 0 if good :0040D43C F7D8 neg eax :0040D43E 59 pop ecx :0040D43F 1BC0 sbb eax, eax ;sets eax = -1 if bad serial else ;(eax = 0) :0040D441 59 pop ecx :0040D442 40 inc eax ;sets eax = 0 if bad serial ;(-1+ 1 = 0) :0040D443 EB02 jmp 0040D447 * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:0040D330(C), :0040D33B(C), :0040D344(C), :0040D34E(C), :0040D377(C) | :0040D445 33C0 xor eax, eax * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040D443(U) | :0040D447 5F pop edi :0040D448 5E pop esi :0040D449 5B pop ebx :0040D44A C9 leave :0040D44B C3 ret Good. Now we see all the interesting parts for us shortly explained. There are three parts. (1) the Stylecheck (2) the generation of the serial (3) the compare (1) STYLECHECK This wonīt take long. :0040D341 80384D cmp byte ptr [eax], 4D ;this compares the first char ;with M :0040D344 0F85FB000000 jne 0040D445 :0040D34A 80780157 cmp byte ptr [eax+01], 57 ;this compares the first char ;with W :0040D34E 0F85F1000000 jne 0040D445 . . . :0040D366 6A2D push 0000002D ;this prepares "-" for the ;compare so we see our serial has to begin with "MW-" Info: 4D is hex for the char M 57 is hex for the char W 2D is hex for the char - (2) THE KEY GENERATION :00412610 53 push ebx :00412611 8B442414 mov eax, dword ptr [esp+14] :00412615 0BC0 or eax, eax :00412617 7518 jne 00412631 :00412619 8B4C2410 mov ecx, dword ptr [esp+10] :0041261D 8B44240C mov eax, dword ptr [esp+0C] :00412621 33D2 xor edx, edx :00412623 F7F1 div ecx :00412625 8B442408 mov eax, dword ptr [esp+08] :00412629 F7F1 div ecx :0041262B 8BC2 mov eax, edx :0041262D 33D2 xor edx, edx :0041262F EB50 jmp 00412681 This is the last part of the keygeneration. This call does the following. It takes the magic number calculated with the Name, and devides it by A (=10). The remainder is in the interval of 0..10 and is the serial number. The result is stored and then reused in the next step. An example will show you everything. Th magic number craeted for Ignatz is 2FACE: 2FACE div A = 4C47... this is used in the next calculation 4C47 div A = 7A0... and so on 7A0 div A = C3 C3 div A = 13 13 div A = 1 1 div A = 0 ... this marks the end. Now lets look at the remainders: 2FACE mod A = 8 (itīs all in hex. Donīt forget) 4C47 mod A = 7 7A0 mod A = 2 C3 mod A = 5 13 mod A = 9 1 mod A = 1 ... this marks the end. Now reading the results starting with the last one we got we get 195278. This is the correct serial! Ok so now all we have to do is find the call which calculates the magic number. How to find this section? Well one possibility is to see where in memory the real serial is kept. To do this, you just need to take a look at the registers. If you see a suspicious call then rightclick the regs and see what they have inside. If itīs the serial then step throug the code again and watch the place in the memory where the serial will be stored. When it appears after a call you know, that you have to follow that call. Follow until you see the characters appearing one by one. Then you have found the last part of the keygen. There are serveral other methods. Take a stroll around and read other tutorials to find them. Also try to develope your own methods. (Watch the push esi and a mov eax, esi befor the call described above) (3) THE COMPARE :0040DEB0 8B542404 mov edx, dword ptr [esp+04] ;real serial :0040DEB4 8B4C2408 mov ecx, dword ptr [esp+08] ;our serial * Possible Reference to Dialog: DialogID_0079, CONTROL_ID:0003, "FtpWolf" | :0040DEB8 F7C203000000 test edx, 00000003 :0040DEBE 753C jne 0040DEFC ;better luck next time * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:0040DEEC(C), :0040DF16(C), :0040DF32(U) | :0040DEC0 8B02 mov eax, dword ptr [edx] ;real serial to eax for compare :0040DEC2 3A01 cmp al, byte ptr [ecx] ;compare first number :0040DEC4 752E jne 0040DEF4 ;if wrong -see you :0040DEC6 0AC0 or al, al ;set flags :0040DEC8 7426 je 0040DEF0 ;if itīs zero weīre done with the compare good guy :0040DECA 3A6101 cmp ah, byte ptr [ecx+01] compare second number :0040DECD 7525 jne 0040DEF4 ;if wrong -see you :0040DECF 0AE4 or ah, ah ;set flags :0040DED1 741D je 0040DEF0 ;if itīs zero weīre done with the compare good guy :0040DED3 C1E810 shr eax, 10 ;get next two numbers of real serial :0040DED6 3A4102 cmp al, byte ptr [ecx+02] ;compare third number :0040DED9 7519 jne 0040DEF4 ;if wrong -see you :0040DEDB 0AC0 or al, al ;set flags :0040DEDD 7411 je 0040DEF0 ;if itīs zero weīre done with the compare good guy :0040DEDF 3A6103 cmp ah, byte ptr [ecx+03] ;compare fourth number :0040DEE2 7510 jne 0040DEF4 ;if wrong -see you :0040DEE4 83C104 add ecx, 00000004 ;get next 4 numbers from input :0040DEE7 83C204 add edx, 00000004 ;get next 4 numbers from real serial :0040DEEA 0AE4 or ah, ah ;set flags :0040DEEC 75D2 jne 0040DEC0 ;finished if zero else jump back to test next numbers :0040DEEE 8BC0 mov eax, eax ;useless * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:0040DEC8(C), :0040DED1(C), :0040DEDD(C), :0040DF0E(C), :0040DF24(C) |:0040DF2D(C) | :0040DEF0 33C0 xor eax, eax :0040DEF2 C3 ret :0040DEF3 90 nop * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:0040DEC4(C), :0040DECD(C), :0040DED9(C), :0040DEE2(C), :0040DF09(C) |:0040DF20(C), :0040DF29(C) | :0040DEF4 1BC0 sbb eax, eax :0040DEF6 D1E0 shl eax, 1 :0040DEF8 40 inc eax :0040DEF9 C3 ret

Name and Reg Number Hashes

Using Cruehead's Crackme.exe (1) as an example, we enter a name and a reg number, then we put BPX GetDlgItemTextA and press the OK button. We pop in Soft Ice and press F12 to reach the following code snippet: :004012B5 push 0000000B :004012B7 push 0040218E ; this will have your name :004012BC push 000003E8 :004012C1 push [ebp+08] * Reference to: User32.GetDlgItemTextA ord:0000h :004012C4 call 004014D0 :004012C9 cmp eax,00000001 :004012CC mov [ebp+10],000003EB :004012D3 jb 004012A1 :004012D5 push 0000000B :004012D7 push 0040217E ; this will have your reg number :004012DC push 000003E9 :004012E1 push [EBP+08] * Reference to: User32.GetDlgItemTextA ord:0000h :004012E4 call 004014D0 You wil be on line :004012C9. Looking before the call to GetDlgItemTextA, we are trying to figure out which of those numbers being pushed in in the area of our Name. It is not likely to be 0000000B or 000003EB (too low in memory), so if we do a "d 0040218E" we see our name in the data window. Let's put a break on it: "bpm 0040218E RW". Although we can also put a break point on 0040217E that holds our reg number, we don't need to do that. Clear the GetDlgItemTextA break point; we don't need it anymore. Press CTRL-D to get out of Soft-Ice. The following is the big picture of the protection scheme: :00401223 cmp eax,00000000 :00401226 je 004011E6 <-- No jump :00401228 push 0040218E <-- Name entered :0040122D call 0040137E <-- Function to trace (Computes Name Hash) :00401232 push eax <-- Save XOR result of name on stack (Name Hash) :00401233 push 0040217E <-- Serial # entered :00401238 call 004013D8 <-- Function to trace (Computes Reg Number Hash) :0040123D add esp,04 <-- Stack Tidy (pop) :00401240 pop eax :00401241 cmp eax,ebx <-- Compare (Compares Name Hash with Reg Number Hash) :00401243 jz 0040124C <-- Jump_good_code Seeing this big picture, we can easily patch the program but we are NOT going to do that; we will study this protection scheme instead. So lets have a trace of 0040137E & 004013D8 :0040137E mov esi,[esp+04] <-- Name entered :00401382 PUSH esi <-- Stack it :00401383 mov al,[esi] <-- al = 1st letter of name :00401385 test al,al <-- Was it 0? :00401387 jz 0040139C <-- Jump_if_it_was (Jump to done) :00401389 cmp al,41 <-- Compare it with 41 or 'A' :0040138B jb 004013AC <-- Jump_below_'A' (If below, jump to bad code) :0040138D cmp al,5A <-- Compare it with 5A or 'Z' :0040138F jnb 00401394 <-- Jump_if_not_below_'Z' (Jump to make it uppercase) :00401391 inc esi <-- Name counter (Now work on next letter of Name ) :00401392 jmp 00401383 <-- Repeat test (go to start of the loop) :00401394 call 004013D2 <-- Jump here if below 'Z' ( make letter uppercase ) :00401399 inc esi <-- Name counter (Now work on next letter of Name ) :0040139A jmp 00401383 <-- Repeat test (Go to start of the loop) So this portion of code will check whether all of the names characters were indeed letters between A and Z, if the letter is lower case the CALL at 00401394 is executed, which consists of the classic sub al,20 i.e. uppercasing routine. So at the end our name is completely uppercased. Here is the CALL we see: :004013D2 sub al,20 :004013D4 mov byte ptr[esi],al :004013D6 ret Note: ASCII Codes: 41h = A; 5A = Z; 61h = a; 7A = z The program checks the current letter of Name. If the Ascii code is below 41h, i.e. characters like '+', we automatically fail the Name check. Next, if the Ascii code of the current letter of Name is higher than 5A, the program jumps to make the uppercase call. Then the ESI points to the next letter in the Name and we continue the test until the current letter is NULL, which is the end of a string. When al finally hold 0, the program jumps to the code that does POP ESI, and now ESI holds our name but all the letters are uppercase. do a 'd esi'. After the POP ESI we see the following code: :0040139D call 004013C2 :004013A2 xor edi,00005678 :004013A8 mov eax,edi :004013AA jmp 004013C1 we continue (still inside CALL 0040137E) with a CALL to function 004013C2. :004013C2 xor edi,edi <-- edi=0. :004013C4 xor ebx,ebx <-- ebx=0. :004013C6 mov bl, byte ptr [esi] <-- Pointer to name. :004013C8 test bl,bl <-- End_of_name_check. :004013CA jz 004013D1 <-- Jump_loop_finished. :004013CC add edi,ebx <-- Add value to EDI which was zero. :004013CE inc esi <-- Counter. :004013CF jmp 004013C6 <-- Loop_again. All the above code does is to take a letter from the Name, make sure it is not a NULL character. If it is, it jumps to the ret; if it is not, it adds the Ascii code of the current letter to edi, then does the same thing for the next letter in the Name. In the end, edi hold the sum of Ascii codes for all letters in the Name. After this call the xor edi,00005678 will xor this sum with 5678. This value is moved to eax to be saved. We will call this value the Name Hash. Then the program jumps to compute the Reg Number Hash. Now lets trace 004013D8 to see how the Reg Number Hash is calculated: :004013D8 xor eax,eax <-- eax=0. :004013DA xor edi,edi <-- edi=0. :004013DC xor ebx,ebx <-- ebx=0. :004013DE mov esi,[esp+04] <-- Serial # entered. :004013E2 mov al,0A <-- al=10. :004013E4 mov bl, byte ptr [esi] <-- Pointer to esi. :004013E6 test bl,bl <-- Check (If bl is NULL, end the call) :004013E8 jz 004013F5 <-- Finished_loop. :004013EA sub bl,30 :004013ED imul edi,eax :004013F0 add edi,ebx <-- edi used for result storage (again). :004013F2 inc esi <-- Name counter. :004013F3 jmp 004013E2 <-- Repeat. :004013F5 xor edi,00001234 <-- Critical xor. :004013FB mov ebx,edi <-- Place the Number Hash in ebx and return for the compare. The first part of the above code simply zeros out the registers and makes esi point to the Reg Number. The last part xor's edi withs 1234, moves the result into ebx, and then returns. The middle part is where we now pay attention to. AL holds OA. The current digit from the Reg Number is moved into bl. If bl = NULL, we end the call. We subtract 30 from bl. What this does is turn the Ascii code of our digit to the actual number, i.e.; if we had entered 1 as the Reg Number, bl would hold 31 and after subtracting 30, it would hold 1. edi is then multiplied by eax (= 0A) and ebx is then added to it. esi is incremented to point to the next digit in Reg Number and the loop is continued. Assuming a five-digit Reg Number in the form of xyzwt, we have: edi = x x * A edi = Ax + y (Ax + y) * A edi = 64x + Ay + z (64x + Ay + z) * A edi = 3E8x + 64y + Az + w (3E8x + 64y + Az + w) * A edi = 2710x + 3E8y + 64z + Aw + t Where x is the first, y the second, z the third, w the fourth, and z the fifth digit in the Reg Number. There is a simple way to get the correct Reg Number for the Name you enter. Put the bpm on the Name and get the Name Hash. If you can't get the Name Hash do a '? eax' on line 00401232. Remember we are using HEX numbers and HEX calculations. Start the Windows Scientific Calculator in HEX mode. Enter the Name Hash, click XOR and then type 1234. What we are doing is doing the registration scheme in reverse, and we know we have to make it equal to the Name Hash. Since xoring with 1234 is the last operation for the Reg Number Hash, we start with that. Let's assume our Name Hash is 5417: 5414 XOR 1234 = 4633. So we know that one middle loop of the Reg Number Hash must make edi = 4623. How do we figure out what values make that. Just use the chart we made before. Look at the last line: edi = 2710x + 3E8y + 64z + Aw + t So we need to devide and use the remainders. First devide 4623 by 2710 and we get 1 (no decimals in HEX!), which means the first digit of Reg Number would be 1. We continue the division for the remainder: 1 * 2710 = 2710. 4623 - 2710 = 1F13 1F13 / 3E8 = 7 (the second digit in Reg Number) 7 * 3E8 = 1B58 1f13 - 1B58 = 3BB 3BB / 64 = 9 (the third digit in Reg Number) 9 * 64 = 384 3BB - 384 = 37 37 / A = 5 (the fourth digit in Reg Number) 5 * A = 32 37 - 32 = 5 (the fifth digit in Reg Number) Thus for a Name Hash equal to 5417, the Reg Number must be 17955 to give the same Reg Number Hash. We can have an easier solution if we realize that we have a loop operation upon our entered Reg Number that is actually converting our decimal number into HEX. For example, with 123456 we get the result 1e240h which is then xor-ed with 00001234 to give 1f074h as the Number Hash, which is placed in ebx to be compared with the Name Hash placed in eax. Realizing this, for a correct Reg Number for Name Hash = 5417, all we have to do is: 5417 XOR 1234 = 4623h = 17955 decimal!

Fixed Registration Numbers!

Do not try to make keygens for programs with Fixed Serial Numbers! You can use different 'techniques' to arrive at the point where the serial number is calculated. The program asks for a name and serial number, so you enter something. For this program, after (unsuccessfully) trying the usual bpx GetWindowTextA and bpx GetDlgItemTextA, you will find out that bpx SendDlgItemMessageA works; bingo! * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040646C(C) | :004064768B442414 mov eax, dword ptr [esp + 14] :0040647A25FFFFFF7F and eax, 7FFFFFFF :0040647F0B442410 or eax, dword ptr [esp + 10] :00406483747E je 00406503 :004064858A01 mov al, byte ptr [ecx] ;al = 1st digit of serial number :004064873C30 cmp al, 30 ;1st digit has to be a 0 (30 = hex for zero) Always watch out fore 'cmp al, 30' or instructions like that. 30 is hexadecimal for zero (0), 39 is 9 decimal. cmp ecx, 00000020 --> Hmm, 20h=" " :004064897578 jne 00406503 ;if it's not a 0, jump to 'bad boy' routine :0040648B0FBED0 movsx edx, al ;edx = 0 :0040648E0FBE7101 movsx esi, byte ptr [ecx+01] ;esi = 2nd digit :004064928BC2 mov eax, edx ;eax = 0 :004064942BC6 sub eax, esi ;eax = 0 minus 2nd digit :0040649683F8FF cmp eax, FFFFFFFF ;FFFFFFFF = -1 so 2nd digit is 1 (-1 = 0 - 1) :004064997568 jne 00406503 :0040649B8B44241C mov eax, dword ptr [esp + 1C] :0040649F25FFFFFF7F and eax, 7FFFFFFF :004064A40B442418 or eax, dword ptr [esp + 18] :004064A87459 je 00406503 :004064AA8A5902 mov bl, byte ptr [ecx+02] ;bl = 3rd digit :004064AD80FB35 cmp bl, 35 ;3rd digit has to be a 5 :004064B07551 jne 00406503 :004064B2385903 cmp byte ptr [ecx+03], bl ;ah, 4th digit has to be a 5 too :004064B5754C jne 00406503 :004064B780790433 cmp byte ptr [ecx+04], 33 ;5th digit has to be a 3 :004064BB7546 jne 00406503 :004064BD0FBE7105 movsx esi, byte ptr [ecx+05] ;esi = 6th digit :004064C18BC2 mov eax, edx ;eax = edx = 0 :004064C32BC6 sub eax, esi ;eax = 0 minus 6th digit :004064C583F8FF cmp eax, FFFFFFFF ;-1 = 0 - 1 so 6th digit has to be a 1 :004064C87539 jne 00406503 :004064CA0FBEC3 movsx eax, bl ;eax = 5 (bl is still 5, see above) :004064CD0FBE7106 movsx esi, byte ptr [ecx+06] ;esi = 7th digit :004064D12BC6 sub eax, esi ;eax = 5 - 7th digit :004064D383F8FF cmp eax, FFFFFFFF ;-1 = 5 - 6 so 7th digit has to be a 6 :004064D6752B jne 00406503 :004064D88B44240C mov eax, dword ptr [esp + 0C] :004064DC25FFFFFF7F and eax, 7FFFFFFF :004064E10B442408 or eax, dword ptr [esp + 08] :004064E5741C je 00406503 :004064E70FBE4107 movsx eax, byte ptr [ecx+07] ;eax = 8th digit :004064EB2BD0 sub edx, eax ;edx = 0 - 8th digit :004064ED83FAFF cmp edx, FFFFFFFF ;-1 = 0 - 1 so 8th digit has to be a 1 :004064F07511 jne 00406503 :004064F280790800 cmp byte ptr [ecx+08], 00 ;check if there isn't a 9th digit (serial is 8 digits!) :004064F6750B jne 00406503 * Possible Reference to Menu: MenuID_0001 :004064F8B801000000 mov eax, 00000001 ;Great! You're a good boy and fully registered! :004064FD5E pop esi :004064FE5B pop ebx :004064FF83C418 add esp, 00000018 :00406502C3 ret Now we know that the serial number has to be 01553161 and that any name can be used.

Registration Numbers with a Pattern (1)

The program asks you to enter a Name and Reg Number. Press OK and a message box tells you Invalid registration number. So 'bpx messageBoxA" , press OK again and you will pop into SoftIce. Press F12 to return to whoever calls the message box. We will have the following code: :100026B1 push 1000D5D8 :100026BB call 10002380 :100026BE add esp,00000004 :100026C0 jne 100026EC ; We see a call and a jump over the message box! :100026C2 push FFFFFFFF * Reference to: USER32.MessageBeep, Ord:0187h :100026C4 call dword ptr[1000f368] :100026CA push 00000000 * Possible String Data Ref from Data Obj -> 'Note' :100026CC push 1000BD54 * Possible String Data Ref from Data Obj -> 'Invalid registration number' :100026D1 push 1000BD08 :100026D6 push esi * Reference to: USER32.MessageBoxA, Ord:0188h We see a call and a jump over the message box. The 'push 1000D5D8' is probably the Reg Number we entered. The 'call 10002380' is probably the Reg Number check. Note: Because there is only one push before this call, most likely the program does NOT use Name in computations to validate registration. Now we have two options here: 1) patch the call, 2) get a valid Reg Number. Let's choose option two this time and get that working Reg Number. So double click on 'push 1000D5D8' in SoftIce to set a bpx on it. When you are on that line display what is being pushed, 'd [address]' and you will see that is the Reg Number you entered. Now press F8 to trace into the 'call 10002380'. We will see the following code: :10002380 push ebx :10002381 mov ecx,FFFFFFFF :10002386 push esi :10002387 sub eax,eax :10002389 mov esi, dword ptr[esp+0C] :1000238D push edi :1000238E mov edi,esi :10002390 repnz :10002391 scasb :10002392 not ecx :10002394 dec ecx :10002395 cmp ecx,000000009 ; ecx holds the length of Reg Number you entered :10002398 je 100023A0 :1000239A xor eax,eax :1000239C pop edi :1000239D pop esi :1000239E pop ebx :1000239F ret The above code should already be self-explanatory to you. Using SoftIce, you can see that esi holds your Reg Number and your Reg Number must be 9 digits long. If yours isn't the eax (registration flag) is set to 0 (not regged) and you return. So go back and enter a 9-digit Reg Number. Now let's look at the code you jump to if your Reg Number is 9 digits long. :100023A0 cmp byte ptr[esi,31] ; esi is the first digit of your Reg Number and :100023A3 je 100023AB ; this first digit must be 1 (ASCII to Decimal) :100023A5 xor eax,eax :100023A7 pop edi :100023A8 pop esi :100023A9 pop ebx :100023AA ret * Reference By a (U)nconditional or (C)onditional Jump at Address: :10002323A3 (C) :100023AB mov al,byte ptr[esi+01] :100023AE cmp al,31 :100023B0 jl 1000244D :100023B6 cmp al,39 :100023B8 jg 1000244D :100023BE mov edi,00000002 :100023C3 mov ebx,00000001 :-- :-- esi is the first digit of the Reg Number which must be = 1. In SoftIce if you give the command '? 31' SoftIce shows that 31 is the ASCII code for decimal 1. So considering what we know about the real Reg Number so far, it should look like 1xxxxxxxx, where x's represent digits we don't know about yet. At line 100023AB esi+01 is the second digit of Reg Number that is moved to al. The program checks to see if the second digit is less than 1 or more than 9 and If it is, registration will fail. So far the Reg Number pattern is 1[1-9]xxxxxxx. Now we must scroll down through the code for a while because it does not concern us. Note that since the code seems to loop a lot, we can do 'd esi' to see the location of esi (for example 0053d6e2) and then do a 'bpr 0053d6e2 0053d6e2+8 RW' bpr is a breakpoint on range, and we entered those two addresses as our range, which covers the entired length of our Reg Number. Thus any time the program accesses any digit of Reg Number, we will pop into SoftIce. Now we can press Ctrl-D a few times and skip following all that looping. Eventually, we will come to code like the following: :100023FE mov al, byte ptr[esi+06] :10002401 cmp al,30 :10002403 je 10002421 :10002405 cmp al,32 :10002407 je 10002421 :10002409 cmp al,34 :1000240B je 10002421 :1000240D cmp al,36 :1000240F je 10002421 :10002411 cmp al,38 :10002413 je 10002421 :10002415 xor eax,eax :10002417 pop edi :10002418 pop esi :10002419 pop ebx :1000241A ret esi+6 is the 7th digit of the Reg Number. Looking at the code, we find that we want to go through one of those je 10002421 otherwise the Reg Flag eax will become zero. Looking at 30, 32, 34, 36, and 38; we know that they are ASCII values for 0, 2, 4, 6, and 8 respectively. With this new information, the Reg Number must be in the form of 1[1-9]xxxx[0,2,4,6,8]xx. :10002421 mov al,byte ptr[esi+07] :10002424 cmp al,31 :10002426 je 1000243E :10002428 cmp al,33 :1000242A je 1000243E :1000242C cmp al,35 :1000242E je 1000243E :10002430 cmp al,37 :10002432 je 1000243E :10002434 cmp al,39 :10002436 je 1000243E :10002438 xor eax,eax esi+07 is the 8th digit of Reg Number and must be 1, 3, 5, 7, or 9. So the Reg Number so far must be in the form of 1[1-9]xxxx[0,2,4,6,8][1,3,5,7,9]x. Now let's look at location 1000243E where the program checks the 9th digit. The program seems to be checking digits in order and therefore will probably ignore digits in 3rd to 6th positions. Later this proves to be true. 1000243E mov al,byte ptr[esi+08] 10002441 pop edi 10002442 sub al,37 ; if al is 37 (7 decimal), the result = 0 10002444 pop esi 10002445 pop ebx 10002446 cmp al,01 ; if at this point al is less than 1, the Carry Flag is set ; To end up with Reg Flag (eax = 1), al must be less than 1 10002448 sbb eax,eax ; take the link to see how eax is set 1000244A neg eax 1000244C ret This is the intersting part of the code. al holds the last (9th) digit of Reg Number. 37 is subtracted from al (Remember al holds the ASCII code so if the 9th digit was 9, al would hold 39.) The program subtracts 37 from al and then compares it to 1 to set the Reg Flag (eax) through the sbb instruction that follows. Thus al must be less than 1 at :10002446 to make eax = -1 when the subsequent instruction 'sbb eax,exe' is carried out. Then 'neg eax' will make eax = 1. The only thing that makes this possible is that the 9th digit in Reg Number be a 7! So our Reg Number must be in the form of: 1[1-9]xxxx[0,2,4,6,8][1,3,5,7,9]7 (x's are arbitrary digits!)

Registration Numbers with a Pattern (2)

Run the program, choose Register, and bpx on GetDlgItemTextA. Softice snaps, and you're tracing. If you display the contents of the registers (indirect addressing of course), you come across this: * Possible StringData Ref from Data Obj ->"BCD5-@??????-CAM1" We now know that our input must be 17 chars long, and is checked against the above "Pattern." Think about it now. What does "?" normally mean? Any value right? So then what does the "@" mean then? Probably some kind of checksum! Anyway, we break into Softice here: * Reference To: USER32.GetDlgItemTextA, Ord:0000h :00492C45 E8E9470000 Call 00497433 ;here :00492C4A 8B4B19 mov ecx, dword ptr [ebx+19] :00492C4D 51 push ecx :00492C4E E851EDFFFF call 004919A4 ;key checking routine :00492C53 59 pop ecx :00492C54 85C0 test eax, eax :00492C56 0F8EEA000000 jle 00492D46 ;evil jump-but don't patch Tracing into the key checking routine (00492C4E), we arrive here... Note: From now, we assume that the key we input is "BCD5-ABCDEFG-CAM1" Remember also that the "Pattern" given is "BCD5-@??????-CAM1" ... ...skipping non-important code... ...and going straight into the key calculation routine... :00491A88 8B55F8 mov edx, dword ptr [ebp-08] :00491A8B 0FBE0A movsx ecx, byte ptr [edx] :00491A8E 83F940 cmp ecx, 00000040 ;checking against the "pattern" to see if we reached '@' (40 hex = '@') :00491A91 7542 jne 00491AD5 ;dont jump if "@" is true :00491A93 0FBE0B movsx ecx, byte ptr [ebx] ;ecx = B :00491A96 0FBE4301 movsx eax, byte ptr [ebx+01] ;eax = C :00491A9A 03C8 add ecx, eax ;ecx = B+C :00491A9C 0FBE5302 movsx edx, byte ptr [ebx+02] ;edx = D :00491AA0 03CA add ecx, edx ;ecx = B+C+D :00491AA2 0FBE4303 movsx eax, byte ptr [ebx+03] ;eax = E :00491AA6 03C8 add ecx, eax ;ecx = B+C+D+E :00491AA8 0FBE5304 movsx edx, byte ptr [ebx+04] ;edx = F :00491AAC 03CA add ecx, edx ;ecx = B+C+D+E+F :00491AAE 0FBE4305 movsx eax, byte ptr [ebx+05] ;eax = G :00491AB2 03C8 add ecx, eax ;ecx = B+C+D+E+F :00491AB4 8BC1 mov eax, ecx ;eax = ecx, preparing to mod :00491AB6 B91A000000 mov ecx, 0000001A ;1A is the quotient...1A = 26 decimal which is the spanning length of the Alphabet (A-Z)! :00491ABB 99 cdq ;make 64 bit :00491ABC F7F9 idiv ecx ;division done...edx has the remainder :00491ABE 83C241 add edx, 00000041 ;0 <= edx <= 25. 41 = "A" therefore we get an Uppercased Alphabet! :00491AC1 8BCA mov ecx, edx ;ecx, the checksum, is an Uppercased Alphabet :00491AC3 8A43FF mov al, byte ptr [ebx-01] ;This is A :00491AC6 84C0 test al, al :00491AC8 7407 je 00491AD1 :00491ACA 0FBED0 movsx edx, al :00491ACD 3BCA cmp ecx, edx ;Compare our "first" input "A" with the correct checksum :00491ACF 7404 je 00491AD5 ;is equal...then good guy...jump :00491AD1 33FF xor edi, edi ;if edi = 0 then bad guy :00491AD3 EB12 jmp 00491AE7 We see that the only values that matters are "ABCDEFG". Ok...lets see what the checksum should be: sum = "B"+"C"+"D"+"E"+"F"+"G" = 19B (hex addition) remainder = 19B mod 1A = 15 checksum = 41+15 = 56 = "V" in ascii Since "A" <> "V", therefore we're the bad guys! Therefore...changing our "A" to "V" will get you regged. Want the Reg Key to include +HCU? sum = "_" + "+" + "H" + "C" + U" + "_" = 1C9 remainder = 1C9 mod 1A = F checksum = 41+F = 50 = "P" in ascii Therefore a valid Reg Code would be "BCD5-P_+HCU_-CAM1"

Tutorial on 2D Arrays for ASM Key Generators

Since The point of this whole thing is to illustrate the use of 2D arrays in ASM Key Generators, that's the only part i'm going to explain, and since this is a really messy thing, i hope you'll concentrate on it. The array, Data_Table, 4 rows of 8 items each, in c would be: char Data_Table[4][8]; Notice that the dimensions of the array are multiples of 2 (2^2=4,2^3=8). This is on purpose to help us later when we try to reference an item from the table. Up to the point of addressing an item in the table, the code just plays around with the serial, twiddeling bits, fooling around. At some point, after the fun is over, values are used to reference the table; this is where explanations starts to be in order. The first thing you, as a newbie, might have trouble with 2D arrays in ASM, is probably the use of the IDIV's in the code. The purpose of these is to limit values to the size of the array. Let me explain this: If we use one value as the line number, in a table of 4 lines, we must make sure that the value isn't larger then 3, ( since lines are counted from 0, as in: 0,1,2,3) this is what the IDIV is used for. Here's a handy little math law for this : "The remainder of a division can't be larger then the number you divide it by." for example: ( "20 % 4" gives the remainder of 20 / 4 = 0 ) 8 % 4 = 0 9 % 4 = 1 10 % 4 = 2 11 % 4 = 3 12 % 4 = 0 Do you see the pattern? The remainder is always smaller then the number u divide by. So after the IDIV's we have 2 numbers limited to the size of the array. The first is divided by 4 and will have a value no larger then 3; the second is divided by 8 and will have a value no larger then 7. So now what do we do with them? we use them to reference the array. How? Easy! we have one value which is the line number (limited to the line numbers by using IDIV). We have another value which is the item number (limited to the number of items). you can think of them as vertical and horizontal; the line number tells us how many lines down, the referenced value is. The item number tells us how many slots to the right the referenced value is located. Now to understand the way we calculate the address of the item number you need to understand that a 2D dimensional array is really only 1-dimensional. confused?! good. Look at this diagram: |1|2|3|4|5|6|7|8| This is meant to represent an 8 item array; it is one-dimensional. We could also look at it as a two-dimensional array: Line 0: |1|2|3|4| Line 1: |5|6|7|8| Why can we do this? Because the array, whether 1- or 2-dimensional, is stored contiguously in memory. As far as the computer is concerned, both the above arrays are stored the same way. We only use 2D arrays because they offer us an easier to represent data in some cases. Enough theory, back to practice. How do we calculate the address of an item, if we have it's line number, and item/offset number? Some of you may know this by intuition. The formula is: (line*item_count) + offset. Let's see some examples: We have a 2D-array, 2 lines, 4 items each. The item we want is in the 2nd line, the item number/offset is 3. REMEMBER! we are counting from 0, IMPORTENT! Thus: line = 1; offset=3; item_count=4; Line 0: |a|b|c|d| Line 1: |e|f|g|h| If we look at the array, this gives us the char "h". Let's put it in the formula: (1*4)+3=7; Let's see how this works with the equivalent 1D-array. |a|b|c|d|e|f|g|h| ^ ^ ^ ^ ^ ^ ^ ^ | | | | | | | | 0 1 2 3 4 5 6 7 So item number 7 gives us "h". The last thing to remember is that the final offset we get from the formula, (7 in the example), is an offset relative to the beginning of the table. That's why you need to add the final item offset to the address of the table in memory. Last Note There's a more efficient way of restricting a value to a range; instead of using IDIV's. If the maximum value is represented by all 1's in binary. 1111b = 15 ( range 0-15, or 16 ) You can either just mask off the extra bits: and ax,1111b, or you can shift the number right 4 places, only 0's are shifted to the left, so you'll get a valid (0-15) number. The same goes for 1b,11b,111b,etc... just change the mask/shift count. Remember that the way i'm using arrays here may not be the usual use of them. I just used some input to generate values which I use to reference table items. If you use arrays for a different purpose, some of this may not apply, but the formula mentioned should always work for you. You can always use the 2D/shr/mask techniques to generate indices into a table.

Tips for Analyzing Key Files

1. The best tool to analyze a key file is an Hex Editor. Text editor are not suitable for this task. 2. A key file is nothing more than a continuous array of bytes inside a small file. These bytes reflect the personal user information (encrypted), checksums of the key file integrity, encrypted dates, names, addresses, encrypted flags (for instance: a flag for multi-user license), etc. 3. To defeat a Key File Based protection scheme, you should create a little program to code a small bogus key file. This generic program should be able to create files under different names a sizes. The content of this bogus file (against most believes) must be readable information. In my case, the bogus key file generator, cuts a large chapter from my favorite literature novel and paste it in the bogus file, adjusting the file size and name according to my necessities. Why readable information inside the bogus file? because the target program will read strategic offset locations of the key file. A readable text inside it will warn about the precise location being read at any time. It will also mark the locations where certain checksums must be added to the final key file. 4. The key file name is in most cases, the target program main executable file name with the extension '*.key'. A very old stupidity flag still available these days. Other strategies to figure out the key file name, are the use of a string searching utility, reading the user's manual (sometimes the author will include instructions on the key file installation, revealing its name), at debugging level, bpint 21 if ah==3d (DOS), when the break occurs, execute: d (e)dx at SoftICE command screen, in windows environment, bpx CreatFile, ReadFile, GetFileAttributesA, etc., will perform the same task. Once you have figured out the key file name, create the bogus file and copy it to the same path as the main target executable. Start the program and see what happens. If you receive an 'Invalid Key File' or 'Corrupted Key File' message (another stupidity flag), your cracking work have been greatly reduced; the next logical step is to search the location of the code where this message is triggered. 5. The key file has to be read. There is no other alternative, the program must read the key file to test its validity. According to this, appropriate breakpoints on file reading interrupts (DOS) or API's (windows) should be set.

Tearing Apart a Keyfile Protection

INTRODUCTION Even though there are many tutors out today regarding keyfile protections, few actually explain the methods of reversing encryption so I will explain here how I did this and I hope it will help at least a few people. TOOLS (I used all of these - we won't need them all for the tutorial) Softice IDA SoftDump - to dump the datatable FileMon Tasm5 if you are going to make a keygen GETTING STARTED Upon running TextPad v3.2.5 and hunting around, we see no way to register it so the next approach is looking for a keyfile. Using Filemon and filtering to only have the txtpad32.exe calls checked, we can see this. Not a bad start! So it's obviously the textpad.lic we are looking for as there isn't anything else remotely interesting in the filemon listing. Go ahead and create a little textpad.lic file in the TextPad directory, as you can see by filemon the program is looking for it in its own directory. (We could also have searched the txtpad32.exe file for strings like .lic, .reg and .key but I prefer to teach you to use other tools as that method won't always work.) Side note, I am using SoftICE as a live approach here, you may prefer to dead list the program using IDA but you won't see some important stuff. I personally trace through it with SoftICE until I understand it and know what values are where, then I dead list it and use the listing+pen+paper to reverse whatever is needed. Now, we need to get access to where TextPad checks the file's contents. It isn't a .INI file so calls like GetPrivateProfileStringA are ruled out mostly. CreateFileA is the best option here as it's called whenever files are opened. Note ReadFile also works. Set your breakpoint on it and run TextPad. When SICE pops for the first time, do a : dd *(ss:esp+4) and you will see the name of the file to be opened, in this case, textpad.lic - good - its the first break. Press F12 twice return to the main routine and you'll land on a test eax, eax which is deciding whether the file was opened successfully or not. This code soon follows. (I have used IDA to get all the code snippits). mov esi, 100h push esi ; 100h bytes to read lea eax, [ebp-170h] ; the FileBuffer push eax ; push for call lea ecx, [ebp-70h] call sub_4A2412 ; leads to ReadFile() mov [ebp-38h], eax ; the number of bytes read cmp eax, esi ; did we get 100h ? jnz short loc_43C41B ; no ? then bad file..... Now go put 100h bytes (or more) of crap in your textpad.lic file to pass this test. The reason you can put more than 100h bytes in the file (although you probably won't want to) is that the program only attempts to read 100h bytes, it doesn't read the whole file then check how much it read. The jnz will only be taken therefore if there are less than the required amount of bytes. Next come the manipulations with the data in the file. THE DECRYPTION STAGE push dword ptr [ebp-28h] ; 2Dh push dword ptr [ebp-24h] ; Looks like a DataTable push dword ptr [ebp+8] push 12h ; a counter (length of string to decrypt) lea eax, [ebp-170h] ; address of start of FileBuffer push eax call sub_43C619 ; decrypt! This repeats, decrypting 3 other sections using the same DataTable etc. but with different constants and different offsets into the keyfile buffer. Don't worry about the fact that the decrypted data still looks rubbish if you see it, we won't be able to correct that until we look at the decryption procedure and reverse it. decrypt 2 : push 28h ; 28h long this time lea eax, [ebp-148h] ; FileBuffer[28h] (as 170h-148h = 28h) decrypt 3 : push 28h ; 28h long again lea eax, [ebp-0F8h] ; FileBuffer[78h] decrypt 4 : push 1Ch ; 1Ch long lea eax, [ebp-0A8h] ; FileBuffer[C8h] So, we have 4 pieces of data in the textpad.lic and we know also their offsets. Let's see what the program does next with them. THE CHECKSUM Straight after the last decryption we have a checksum routine, with some adding going on in 1 location and xoring in another. loc_43C41B: mov dword ptr [ebp-14h],0 ; clear the counting variable mov eax, [ebp+8] ; address of the data from FileBuffer[0] mov ecx, [eax] ; ecx points to it now mov esi, [ecx-8] ; esi holds length of the decrypted junk cmp [ebp-14h], esi ; is there anything ? jge short loc_43C447 ; if not go to next checksum mov eax, [ebp-4Ch] ; not necessary loc_43C432: mov edx, [ebp-14h] ; edx = count movzx eax, byte ptr [ecx+edx] ; eax = byte[string+count] xor [ebp-18h], eax ; [ebp-18] starts at 0FFFFFFFFh add [ebp-1Ch], eax ; add byte to another var(which starts at 0) inc dword ptr [ebp-14h] ; increase counter cmp [ebp-14h], esi ; reach end of string yet ? jl short loc_43C432 ; go back for more TextPad now goes on to checksum the other 3 parts of the decrypted file in the same manner but the addition and xoring still take place in [ebp-18h] and [ebp-1Ch]. When this has all been done, we reach the validation section. loc_43C4CB: mov eax, [ebp-18h] ; the xor result sub eax, [ebp-14Ch] ; take away a value in the FileBuffer[24h] from it cmp eax, 1 ; is eax now 1 ? sbb eax, eax neg eax ; eax*-1 mov [ebp-2Ch], eax ; store result At this point I took a brief look down at the next section of the validation which uses the addition checksum. I quickly realised that we do not have to bother AT ALL with the xoring checksum because this next part OVERWRITES the result of the xor validation. This section too stores it's results in [ebp-2Ch]. See the line marked * mov eax, [ebp-38h] ; eax = 100h add [ebp-1Ch], eax ; add it to the addition checksum mov eax, [ebp-1Ch] ; eax = new addition checksum sub eax, [ebp-14Ch] ; eax - DWORD in FileBuffer[24h] cmp eax, 1 ; is eax = 1 ? sbb eax, eax neg eax mov [ebp-2Ch], eax * You can just trust me on this part, TextPad checks a little further on in the code whether the value in [ebp-2Ch] is equal to 0, if it is, the program is assumed to be registered. From this we know that the checksum value is stored at FileBuffer[24h] and is 100h greater than the addition of all the bytes in the 4 decrypted sections of the keyfile. The checksum itself has not been encrypted, so we don't need to worry too much about it, its pretty easy to calculate. THE DECRYPTION/ENCRYPTION So far we have understood how the program decides if it's registered, and we know what is supposed to be where in our textpad.lic file. However, the one thing we haven't looked at yet is the actual decryption. Before we even start this, we could make a pretty good guess that this will involve reversing the decryption so we can encrypt some data ourselves to make a valid keyfile. So, retrace through, and delve into one of the decryption calls, the first one would be best as any values will be unaltered, the other calls may use values generated by this one as we will see. 14 lines into the call, the main decryption begins. loc_43C63A: mov ecx, dword ptr [4ED6A0] ; a counter mov eax, [ebp+arg_C] ; datatable mov cx, [eax+ecx*2] ; word from table mov ebx, 3 ; ebx = 3 xor cx, [esi] ; word[table] ^ word[keyfile] mov eax, dword ptr [4ED6A0] shr cx, 4 ; shift right 4(which is dividing by 16) cdq ; prepare for division idiv ebx ; divide eax by ebx (counter/3) test edx, edx ; is there a remainder ? jnz short loc_43C669 ; if so then jump mov ax, 0FFh sub ax, cx mov cx, ax loc_43C669: ;at this point, cl holds the decrypted byte cmp cx, 60h ; 60h is used as a termination for the encrypted string as if the decrypted byte = 60h jz short loc_43C68F ; this jump takes us out of the loop mov eax, dword ptr [4ED6A0] ; the counter again add esi, 2 ; esi+2 so we get the next word, not byte inc eax ; increase eax cdq idiv [ebp+arg_10] ; divide eax by 2Dh, remainder in edx mov eax, [ebp+arg_4] mov dword ptr [4ED6A0], edx ; store count (max is 2Ch because once it reaches 2Dh, remainder is 0) mov [edi], cl ; store decrypted byte in buffer dec [ebp+arg_4] ; decrease a value (the 12h, 28h and 1Ch) inc edi ; prepare buffer for next decrypted byte test eax, eax ; was the [ebp+arg_4] = 0 jnz short loc_43C63A ; if not, decrypt more We have two cases from the above code. One if the remainder of the count/3 is 0 and the other if it is not. This decides which path through the code is taken. Thats all the code we need to analyse and reverse the decryption. The next steps are exactly what I wrote down on pieces of paper when I reversed this. It is a kind of pseudocode, the first step is built from the decryption, then I reverse it. 1st case - the remainder of count/3 is not 0 - jnz loc_43C669 is taken. This is the pseudocode for the decryption where 'string' is the decrypted string, 'keyfile' is the data in the keyfile and 'table' is the datatable. For those who don't know, ^ is used as a symbol for XOR. Our aim while reversing is to have on one side word[keyfile] and everything else on the other side as we are trying to calculate the word[keyfile] byte[string] = (word[table+count*2] ^ word[keyfile]) SHR 4 following from this we can say... byte[string] SHL 4 = word[table+count*2] ^ word[keyfile] (byte[string] SHL 4) ^ word[table+count*2] = word[keyfile] That wasn't difficult at all. 2nd case - the remainder of count/3 is 0 - jnz loc_43C669 is not taken. Again using the dead listing, we can build the following statement. byte[string] = 0FFh - (word[table+count*2] ^ word[keyfile]) SHR 4 so.... byte[string] - 0FFh = -(word[table+count*2] ^ word[keyfile]) SHR 4 this is slightly inconvenient in this format, so we will change the signs to make it easier by multiplying by -1. 0FFh - byte[string] = (word[table+count*2] ^ word[keyfile]) SHR 4 (0FFh - byte[string]) SHL 4 = word[table+count*2] ^ word[keyfile] [(0FFh - byte[string]) SHL 4] ^ word[table+count*2] = word[keyfile] That's it! We have reversed the decryption and turned it into the encryption, and it wasn't at all difficult. The last thing we should do is turn it into a keygen. This took me 1 hour to write in win32asm (because I am a slowass and had not used win32 to create/open/write to files before) and about 30 minutes to fix little things in it. Just note one thing about the decryption procedure. It reuses the counter in the following decryptions as the variable in which it is stored is never cleared. I made the mistake of not noticing this, and it took me about 20 minutes to realise this after I coded the keygen.


Memory Address Pointers (Call Relocation Tables)

Using Memory pointers is a simply way to make life hard for newbie crackers when 
they try to follow the program's flow in their dead listings.  They are also very 
useful from the programmer's point of view because it's quite easy to perform 'self 
modifying' code techniques on the program itself,  now that would make things very 
interesting indeed but that's a totally different story!

Here's a simple overview of how Memory Address Pointers work: 

The programmer sets aside an area of memory that he will use to hold the locations 
where some of his routines can be found. Lets call this area Table 1. These memory 
addresses will be stored side-by-side and don't contain any assembler instructions 
such as jmp, jnz etc. 

Suppose the target program has three routines, Routine 1, Routine 2, Routine 3 which 
the programmer wants to place their memory locations in a Table 1 then here's how 
this could be achieved:

Routine 1. Begins at memory address 4703ED32 
Routine 2. Begins at memory address 416EDD00 
Routine 3. Begins at memory address 445F3400 

OK, we have three routine and we know where to begin, normally a program would use 
CALL 4703ED32 to execute the first routine or perhaps use a JNZ 4703ED32 which we 
all should be familiar with by now..  However, the programmer doesn't want you to 
see this in your dead listing so he will replace the normal Call and JNZ statements 
with ones that cannot be calculated easily by W32Dasm. 

The programmer will 'take' the memory address of the above three routines and store 
them in an area within the program which we've called Table 1. 


32ED0347  00DD6E41   00345F44 
     :                   :                    : 
     :                   :                    :------------Routine 3 
     :                   :-----------------------------Routine 2 
     :---------------------------------------------Routine 1 

Notice how the routine addresses are 'reversed', that's how 
they are stored in PC's dating back to the early days of computers.
OK, now the programmer has created a kind of look-up table but the rest of his 
program now needs to be told WHERE and HOW to 'find' the addresses for Routine 1, 
Routine 2, Routine 3.

First, the programmer will use one of the PC's internal registers to 'point' to 
Table 1. 

Mov  EAX,40100000    <-------  Set Register EAX to point to Table 1 

If you type d EAX then you'll see this:- 

XXXXXXXX:40100000    32ED034700DD6E4100345F44 

EAX is 'Pointing' to Table 1 

XXXXXXXX:40100000  32 
XXXXXXXX:40100001  ED 
XXXXXXXX:40100002  03 
XXXXXXXX:40100003  47 
XXXXXXXX:40100004  00 
XXXXXXXX:40100005  DD 
XXXXXXXX:40100006  6E 
XXXXXXXX:40100007  41 
Now in order for the program to execute say, Routine 2 then it has to give the 
computer a little math's sum to tell it where to find the start of Routine 2. 
CALL ptr [EAX+00000004] 
           :      : 
           :      :--------Memory offset =Routine 2 Address 
           :---------------EAX           =Table 1 
Table 1. 

XXXXXXXX:40100000  32 ---  First Memory address at 40100000 offset 00 - Routine 1 
XXXXXXXX:40100001  ED : 
XXXXXXXX:40100002  03 : 
XXXXXXXX:40100003  47 : 

XXXXXXXX:40100004  00 ---  Second Memory address at 40100004 offset 04 - Routine 2 
XXXXXXXX:40100005  DD : 
XXXXXXXX:40100006  6E : 
XXXXXXXX:40100007  41 : 
Looking at the above Call ptr [EAX+00000004] instruction we can see that the program 
will take the address of Routine 2 out of Table 1 by adding the offset value 
[00000004] in order to retrieve the memory address stored in this table. 

Since memory addresses are stored in REVERSE order we need to reverse the order of 
these four bytes to get the correct address.

I was recently fumbling around with my copy of Screen Ruler and I was puzzled 
by a small protection. In fact the code of this target checks for the existence of 
two files on your hard disk: its registration file and another help file. If you 
delete them, it snaps.

This is a very common scheme, since many shareware authors like their products to be 
distributed with all their parts. Usually a simple run of filemonitor will alert you 
about this and land you directly with a good search string for your dead listing. 

The protection inside this target is indeed absolutely simple, yet we'll use it in
order to learn another very useful trick for our reverse engineering purposes: the 

Here is the part of the code where Screen Ruler checks the "existence" of its 
"registration file" (sreg.txt) and of, its "text" help file (sruler.txt). Here is my 
SYNTHETIZED listing of this part of the code, please note the two lines in red (who 
calls it and where it returns to?)
:00401739      push ebp                     ;who calls here? THIS IS IMPORTANT
...                                         ; ->"Sorry, this version of Screen 
...                                         ;"->"Ruler is incomplete."
:0040176F      Call dword ptr [0040E324]    ;FindExecutableA   ->"SREG.TXT"
:00401775      mov ebx, eax                 ;pass return value
:00401777      cmp ebx, 2                   ;file was not found
:0040177A      jne 0040178D                 ;check_next_file if found
...                                         ;not found, so:
:0040177E      Call dword ptr [0040E36C]    ;USER32.MessageBeep
...                                         ;->"Error Notice"
:0040178B      jmp 004017BC                 ;messagebox, flag 0 and ret

:004017A0      Call dword ptr [0040E324]    ;FindExecutableA  ->"SRULER.TXT"
:004017AB      jne 004017CF                 ;flag 1 and ret if found
...                                         ;else
:004017AF Call dword ptr [0040E36C]         ;MessageBeep,
...                                         ;->"Error Notice"
:004017C5 Call dword ptr [0040E370]         ; USER32.MessageBoxA
:004017CB xor eax, eax                      ;bad flag zero
:004017CD jmp ret
:004017CF mov eax, 1                        ;good flag one
:004017DB ret                               ;Ja, where? THIS IS IMPORTANT  <=====

Well, there is not much to say about this codesnippet... the checking uses a 
function that is really intended for EXECUTABLES just in order to check for the 
presence of the other two files of the "bundle". As you should immediately see here, 
you can easily trick this code into returning a good flag, or, as I said, you can 
write your own "faked" files...  But, just wait a minute: would it not be nice if we 
could find WHERE this snippet is called from in our target? Yes, sure, but how are 
we going to know FROM THE DEAD LISTING where the call did come from? There is 
(apparently) nowhere in the code a call to the beginning of the above code snippet 
at :00401739. No jumping here, no calls, how did the target get here?

In order to find where the above code snippet is called from we'll have to have a 
look at the CODE TABLES of our target (use hexworkshop and search for the hex string 
"39174000", i.e. the start location of the procedure above... our start location 
in INVERTED hex sequence):

7DC0: 00000000000000000000000000446174
7DD0: 610000000000000000002E354000E112       ;table begins at 7DDA, duh
7E40: 40006719400039174000FB3340000000       ;table ends at 7E4A (+70), duh
7E50: 5555AAAA5555AAAA5555AAAA5555AAAA

You dig it, don't you? In fact these tables, near the bottom of almost all windows 
files, are very useful for our reverse engineering purposes (that's the real reason 
I wanted to show you the protection snippet above)... looking at them you can 
immediately get the addresses of various important calls: here you have

:0040352E      ;at the bottom
:004012E1      ;at the bottom+4
:004013BF      ;at the bottom+8
:403164        ;at bottom+64
:401967        ;at bottom+68
:401739        ;(our nag call) at bottom+6C
:4033FB        ;at the top (=+70)

And you will now be able to easily examine each one or the other... Yet you can do 
even more! Much more! Since you can see the "bottom" of this table at 7DDA, you'll 
know that you have here 7DDA = :0040352E, 7DDA+4 (=7DDE= :0040121E), 7DDA+8 (=7DE2 = 
:004013BF)... until you get to our 7E46= :00401739_call_nag_if_missing_file, which 
is 7DDA+6C. You dig it: each 4 bytes we have, in this table, is the beginning of a 

Why should we care! Will you cry exasperated by all this. Well, look at this nice 
piece of code, inside our target, that is not supposed to tell us anything, look: 

:004012E1 55                   push ebp
:004012E2 89E5                 mov ebp, esp
:004012E4 53                   push ebx
:004012E5 83EC04               sub esp, 00000004
:004012E8 894DF8               mov dword ptr [ebp-08], ecx
:004012EB 89C8                 mov eax, ecx
:004012ED 8B4D08               mov ecx, dword ptr [ebp+08]
:004012F0 894804               mov dword ptr [eax+04], ecx
:004012F3 89C1                 mov ecx, eax
:004012F5 8B19                 mov ebx, dword ptr [ecx]
:004012F7 FF536C               call [ebx+6C]   ;HERE!
:004012FA 89C1                 mov ecx, eax
:004012FC 09C9                 or ecx, ecx
:004012FE 7502                 jne 00401302
:00401300 EB26                 jmp 00401328

We could just look at this code snippet until our eyes started watering, and we 
would never understand anything without some laborious softicing. Or would we?  In 
fact this is NOT a protection trick, it is the result of the COMPILATION of our 
target, yet it amounts to the same "confusing" effect for us reverse engineers if 
you will not use the "Relocation Table" countermeasures I'm explaining to you now.

OK, I know, you already understood the whole point. Let's rewrite the "awful" code 
snippet above in "our" way: 

:004012E1 55            push ebp   ;value corresponds to a "call [ebx+4]" somewhere
...                                         ;prepare call
:004012F7 FF536C        call [ebx+6C]       ;call table value        
                                            ; 00401739_nag_if_missing_file
:004012FA 89C1          mov ecx, eax        ;get return value
:004012FC 09C9          or ecx, ecx         ;is it zero (=bad)
:004012FE 7502          jne 00401302        ;no, continue
:00401300 EB26          jmp 00401328        ;yes, beggar off

Well, it makes MUCH more sense, doesn't it?... now, what this has got to do with our 
"dead listing?"  Quite a lot, I would say.  There is no need whatsoever to fire up 
winice in order to understand such "awful" dead listing snippets any more. In fact, 
who cares about the values in ebx and how they get there, and all the mischievous 
(or innocent) manipulations of those values any more? Once we know that there is a 
base for all relocated calls, those values "should" correspond (and indeed, almost 
always, they will) to the values inside our table of calls.

Thus the cryptic "call [ebx+4C]" will correspond (here in our target) to a simple 
call 7E26 (i.e.:004026EE), and the mysterious "call [ebx+64]" will simply call the 
routine value hardcoded at 7E3E (i.e.:00403164) and so on! Everything is SO easy! 

Remember this, once for all:  You should ALWAYS have a look at these tables when you 
dead list a program, you should ALWAYS calculate their base and you should ALWAYS 
prepare a copy of said table, in order to integrate it to your "dead listing".

Use this trick to give meaningful tags to the numerous "relocated" calls that you'll 
find inside the snippets of your compiled targets... you'll notice pretty soon the 
incredible difference that this will mean for your understanding of the code!

Your wordprocessor's search and replace facilities will be more useful than ANY 
other tool when trying to "feel" your targets, and you will feel the code, provided 
you have the necessary peace of mind: do not, ever, ran amok inside the dark 
codewoods, they are full of thorns.  Learn the names of the common structures and 
give meaningful names to all things that you discover along your journey. If you 
have done your homework, a brain and a pencil are all what you will need to crack 
ANY target.  Well, I love working with my brain and my colored staedtler pencils! 

String Fishing Using a HEX Editor and W32dasm (Part 1)

Here's a way to find strings that you can't find using W32dasm, but that you can 
find using a hex-editor. This can be very useful for nags, here is an example how to 
do it. Say we've found that string using a hex editor at offset B1602. Then open 
w32dasm to get the info where the DATA segment offset is located, you can find this 
info at the first page of the deadlisting. Say it is located at AC400. Now we 
subtract the DATA segment offset from the string offset (B1602-AC400=5202) and add 
this to 004C0000. But why 4C0000? Well, that's because the image base starts at 
00400000h (get this info using w32dasm) for most programs, and in our example
object DATA is located at the RVA(relative virtual address) C0000.

So: 00400000 + 000C0000 = 004C0000 and at the end we add 5202 to this to get 
004C5202. This is the address where our string is located. This tip is very useful 
when you can't find the string with W32dasm, but can with a hex editor. This is very 
handy to find out where that naggy message/error) gets pushed onto the stack. It's 
easy to find it now(in this example it will be a "push 004C5202").

String Fishing Using a HEX Editor and W32dasm (Part 2)

When you start the program, you see just the message box: 
You have exceeded the playing limit. .bla-bla-bla..
And after pressing OK comes first the nag dialog and then the editor too. 

Let's disassemble the program. Pressing ImpFn we then look for our dialogBox(). 
Surprise - we see it along with the similar one dialogBoxIdirect()), but only at one 
place in the listing, one after another! This means, that ALL dialogs in this 
program - and there are many! - come as parameters passed to the function containing 
both dialogbox() procedures - and the dialogBox() is called at only one place, yet 
with different parameters for the different dialogs. This a clever approach, since 
it first:

- spares exports/ load time and second - and more important for us:
- forces us search for our dialogs on a "higher" level, introducing more difficulty!

What should we do?
A first approach is to find where in the body of the program the message box with 
the above text is issued, in the hope that we will land somewhere in the protection 
scheme as well. In order to do it we can search for the string "exceeded" and for 
references to it. And here I made a mistake that cost me lots of time and work: I 
tried the W32DASM's "Find Text" and found nothing, while pretty easily located the 
word with the Hexeditor in the program. Why? Because this is a 16-bit program with 
14 different segments, as we can see at the beginning of the disassembly: 

---------------- SEGMENT INFO ---------------
Number of Code/Data Segments = 14

  CSEG001  File Offset: 00001000  Size:B7D7  Flags:0x1D50 -> CODE, MOVEABLE
  CSEG002  File Offset: 0000CA00  Size:B68E  Flags:0x1D50 -> CODE, MOVEABLE
  CSEG003  File Offset: 00018200  Size:C4C6  Flags:0x1D50 -> CODE, MOVEABLE
  CSEG004  File Offset: 00024800  Size:E794  Flags:0x1D50 -> CODE, MOVEABLE
  CSEG005  File Offset: 00033200  Size:E4E3  Flags:0x1D50 -> CODE, MOVEABLE
  CSEG006  File Offset: 00041C00  Size:A868  Flags:0x1D50 -> CODE, MOVEABLE
  CSEG007  File Offset: 0004CC00  Size:C33C  Flags:0x1D50 -> CODE, MOVEABLE
  DSEG008  File Offset: 00000000  Size:0000  Flags:0x0C51 -> DATA, MOVEABLE
  DSEG009  File Offset: 00000000  Size:0000  Flags:0x0C51 -> DATA, MOVEABLE
  DSEG010  File Offset: 00000000  Size:0000  Flags:0x0C51 -> DATA, MOVEABLE
  DSEG011  File Offset: 00000000  Size:0000  Flags:0x0C51 -> DATA, MOVEABLE
  DSEG012  File Offset: 00000000  Size:0000  Flags:0x0C51 -> DATA, MOVEABLE
  DSEG013  File Offset: 00000000  Size:0000  Flags:0x0C51 -> DATA, MOVEABLE
  DSEG014  File Offset: 00059400  Size:92D0  Flags:0x0D51 -> DATA, MOVEABLE

Neither 'Find Text' nor 'StrRef' in W32DASM can help us; we have to look directly in 
the data segments! There are 7 of them, but only 1 actually contains data. We can 
see this by pressing the 'HexData' button/ menu item. Browsing trough the segments 
we soon find in

"Data Segment 14.... Page 1 of 5" the following: 

:0014.0138 6D 00 59 6F 75 20 68 61  m.You ha <=========== HERE!
:0014.0140 76 65 20 65 78 63 65 65  ve excee
:0014.0148 64 65 64 20 74 68 65 20  ded the 
:0014.0150 70 6C 61 79 69 6E 67 20  playing 
:0014.0158 6C 69 6D 69 74 2E 20 20  limit.
:0014.0160 53 65 65 20 74 68 65 20  See the 
:0014.0168 66 6F 6C 6C 6F 77 69 6E  followin
:0014.0170 67 20 6F 72 64 65 72 20  g order 
:0014.0178 69 6E 66 6F 20 66 6F 72  info for
:0014.0180 20 64 65 74 61 69 6C 73  details
:0014.0188 2E 00 6E 6F 74 65 70 61  ..notepa <=========== AND THIS ONE TOO!
:0014.0190 64 20 6F 72 64 65 72 2E  d order.
:0014.0198 66 72 6D 00 77 62 00 25  frm.wb.%
:0014.01A0 64 00 44 32 00 54 31 00  d.D2.T1.
:0014.01A8 53 79 73 74 65 6D 20 69  System i
:0014.01B0 6E 73 74 61 6C 6C 61 74  nstallat
:0014.01B8 69 6F 6E 20 65 72 72 6F  ion erro

What do you mean? The error message, followed immediately by the command line for 
launching the editor! Hot! But how do we proceed further?

Look, to display a message, any Windows procedure needs a pointer to that string 
passed as parameter to the procedure. And what does a pointer look like? It is just 
the offset of the string within some data segment. Since we see above that "our" 
offset is 013A, we do following search: 

Find Text mov ax, 013A (there must be ONE space between the 
comma and the 013A) 

Note, that only Find Text 013A would do it too, but there are too many irrelevent 
occurences of it. Now Wdasm searches and what? Believe it or not;  there is only one 
mov ax, 013A in the whole program: (Added Note : We could also have "push 013A.") 

:0001.07C9      3D1E00  cmp ax, 001E
:0001.07CC      7303    jnb 07D1
:0001.07CE      E98800  jmp 0859

:0001.07D1      B83A01  mov ax, 013A <========== HERE!
:0001.07D4      8CDA    mov dx, ds
:0001.07D6      52      push dx
Is that what we need? we can try it! Right above the mov there is a jump-around 
construction, lets change it: 

from  :0001.07CC      7303     jnb 07D1
to    :0001.07CC      7300     jnb 07CE

thus forcing program flow to the mysterious 0859 location.

Load the program in the hexeditor, go to location 000017CC (you take it from the 
status line of the Wdasm!), patch it and save. Now run the game and what? It starts 
with the nag set to "Time expired" but no more limits there! Wasn't it easy?

String Fishing Using a HEX Editor and W32dasm (Part 3)

At this time I will explain how the protection works to see how many things we still 
have to do. The file clock.tim is not so relevant for the protection. You can delete 
it, but the program creates it every time anew. It serves only for comparison with 
other data.

The main source for the protection is of course the registry. The program creates a 
new key under HKEY_CLASSES_ROOT named Accessoires. Then it adds to it a subkey 
Clock, which in turn contains tree subkeys: D1, D2 and T1. These subkeys contain 
string values, accordingly to the following schema: 

D1 represents the date of the last-but-one start of the program; 
D2 represents the date of the very last start; 
T1 represents the total times you used it. 

So even if you "turn back the time", the program recognize it and refuses starting.
And now look: What happens, when you, WITHOUT any intents turn the clock back and 
try the game (this happens automaticaly when you are playing late night on the day-
time savings. You will be never able to start normally this program again, even 
within the "allowed" time limit!

With this in mind we proceed with cracking. Now, where the game works again, we are 
going to crack first the whole protection, and then we will remove tha nags in order 
to save our disk space (and for learning purpose, of course!)   We can of course 
simply delete the Accessoires key in the registry, or, for more fun just copy the 
value of D2 into D1, but since they are created every time anew and the registry 
becomes more and more a carbage colector we should try these keys to be not created 
at all.

If you look closer at the HexData dump above, you can see three other messages 
complaining about some "errors": 

System error - conflicting dates... at offset 00A9 - this occures when the date in 
the Clock.tim does not correspond to the one in the Registry; 

System date error... at offset 00F9 - when you turn your system time back, and
System installation error... at offset 01A8 - if the registry operation fails 

We can easily locate the references to the strings using the String Fishing approach 
(Part 2): 'Find Text' mov ax, String_Offset  i.e. searching for mov ax, 00A9 will 
find this: 

:0001.068C       8B86B2FD       mov ax, [bp+FDB2]
:0001.0690      3986C0FC        cmp [bp+FCC0], ax
:0001.0694      7503            jne 0699
:0001.0696      E97500          jmp 070E

:0001.0699      B8A900          mov ax, 00A9  <====== HERE!
:0001.069C      8CDA            mov dx, ds
:0001.069E      52              push dx
:0001.069F      50              push ax

There is still one occurrence apiece, so you can't do anything wrong. The locations 
are close to each other and everyone has a short overhead with a jump-around chain, 
followed by a WINEXEC() call. This means, the program checks everytime for the 
current condition, and, if "not good" puts the appropriate message, otherwise it 
jumps to the next check.

Do we need to crack ALL these checks separately in order to get our program run 
without any nags? You can try it, of course, and it will work (be aware to patch the 
right jump/conditional jump, it is always the FIRST one of a chain; sometimes you 
should patch ALL the jumps!) but this will be a hell of a job; furthermore our goal 
should be to crack it so that no more registry keys will be added/no files created! 
This is done generally by jumping over the culprite code to the location where the 
"real" program begins. So we need TWO locations: the first where we should jump 
from, and the second where we're jumping to. How do we find them? 

The latter one is easy. Browsing through the code around the checks we soon find the 
calls that handles the registry itself: 
:0001.0470      9AFFFF0000      call SHELL.REGQUERYVALUE

:0001.04AC      9A71040000      call SHELL.REGQUERYVALUE
Just after the LAST registry call at: 
:0001.0B6F      9A79090000      call SHELL.REGCLOSEKEY

:0001.0B74      B80302          mov ax, 0203  <===< HERE
:0001.0B77      8CDA            mov dx, ds

we may suppose that the deverse checks are finished and is a good place for jumping 
to. You can call it Zen, if you want, however I have tryed it and it works. Jumping 
to it from somewhere above the checks puts one through the "back door" directly in 
the "restricted area"! 

Now we have our "entry point". What is left for us to find out is the location, 
where to place the jump itself. There are lots of registry calls in that code chunk, 
creating, seting, and reading variose keys. But where exactly are the relevant keys 
created? The string "Accessiores" doesn't appear in the disassembly or in the 
HexData, nor can you find it even with a hex-search in the exe or in the DLLs. The 
same with the "clock.tim". Mystery?

Look at this beautiful piece of code, just before the first registry-related call: 
:0001.03C2      C68618FF41      mov byte ptr [bp-00E8], 41;     A
:0001.03C7      C68619FF63      mov byte ptr [bp-00E7], 63;     c
:0001.03CC      C6861AFF63      mov byte ptr [bp-00E6], 63;     c
:0001.03D1      C6861BFF65      mov byte ptr [bp-00E5], 65;     e
:0001.03D6      C6861CFF73      mov byte ptr [bp-00E4], 73;     s
:0001.03DB      C6861DFF73      mov byte ptr [bp-00E3], 73;     s
:0001.03E0      C6861EFF6F      mov byte ptr [bp-00E2], 6F;     o
:0001.03E5      C6861FFF72      mov byte ptr [bp-00E1], 72;     r
:0001.03EA      C68620FF69      mov byte ptr [bp-00E0], 69;     i
:0001.03EF      C68621FF65      mov byte ptr [bp-00DF], 65;     e
:0001.03F4      C68622FF73      mov byte ptr [bp-00DE], 73;     s
:0001.03F9      C68623FF5C      mov byte ptr [bp-00DD], 5C;     \
:0001.03FE      C68624FF43      mov byte ptr [bp-00DC], 43;     C
:0001.0403      C68625FF6C      mov byte ptr [bp-00DB], 6C;     l
:0001.0408      C68626FF6F      mov byte ptr [bp-00DA], 6F;     o
:0001.040D      C68627FF63      mov byte ptr [bp-00D9], 63;     c
:0001.0412      C68628FF6B      mov byte ptr [bp-00D8], 6B;     k
:0001.0417      C68629FF00      mov byte ptr [bp-00D7], 00; <== terminates it
Don't you see anything? This is the same old trick the protectionists try to fool us 
with since the very first days! Now look again, after I've "commented" it for you, 
together with the other relevant place: 

and here: 
:0001.00CC      8346E001        add word ptr [bp-20], 0001
:0001.00D0      8D9EEAFC        lea bx, [bp+FCEA]
:0001.00D4      03D8            add bx, ax
:0001.00D6      C6075C          mov byte ptr [bx], 5C;    \ <=====
:0001.00D9      8B46E0          mov ax, [bp-20]
:0001.00DC      8346E001        add word ptr [bp-20], 0001
:0001.00E0      8D9EEAFC  ;      lea bx, [bp+FCEA]
:0001.00E4      03D8            add bx, ax
:0001.00E6      C60743          mov byte ptr [bx], 43;    C <=====
:0001.00E9      8B46E0          mov ax, [bp-20]
:0001.00EC      8346E001        add word ptr [bp-20], 0001
:0001.00F0      8D9EEAFC        lea bx, [bp+FCEA]
:0001.00F4      03D8            add bx, ax
:0001.00F6      C6074C          mov byte ptr [bx], 4C;    L <=====
:0001.00F9      8B46E0         bsp; mov ax, [bp-20]
:0001.00FC      8346E001        add word ptr [bp-20], 0001
:0001.0100      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0104      03D8      p;      add bx, ax
:0001.0106      C6074F          mov byte ptr [bx], 4F;    O <=====
:0001.0109      8B46E0          mov ax, [bp-20]
:0001.010C      8346E001        add word ptr [bp-20], 0001
:0001.0110      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0114      03D8            add bx, ax
:0001.0116      C60743          mov byte ptr [bx], 43;    C <=====
:0001.0119      8B46E0          mov ax, [bp-20]
:0001.011C      8346E001        add word ptr [bp-20], 0001
:0001.0120      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0124      03D8            add bx, ax
:0001.0126      C6074B          mov byte ptr [bx], 4B;    K <=====
:0001.0129      8B46E0          mov ax, [bp-20]
:0001.012C      8346E001        add word ptr [bp-20], 0001
:0001.0130      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0134      03D8            add bx, ax
:0001.0136      C6072E          mov byte ptr [bx], 2E;    . <=====
:0001.0139      8B46E0          mov ax, [bp-20]
:0001.013C      8346E001        add word ptr [bp-20], 0001
:0001.0140      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0144      03D8            add bx, ax
:0001.0146      C60754          mov byte ptr [bx], 54;    T <=====
:0001.0149      8B46E0          mov ax, [bp-20]
:0001.014C      8346E001        add word ptr [bp-20], 0001
:0001.0150      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0154      3D8             add bx, ax
:0001.0156      C60749          mov byte ptr [bx], 49;    I <=====
:0001.0159      8B46E0          mov ax, [bp-20]
:0001.015C      8346E001        add word ptr [bp-20], 0001
:0001.0160      8D9EEAFC        lea bx, [bp+FCEA]
:0001.0164      03D8            add bx, ax
:0001.0166      C6074D          mov byte ptr [bx], 4D;    M <=====
:0001.0169      8B46E0          mov ax, [bp-20];    <====== Here is the index!
:0001.016C      8346E001        add word ptr [bp-20], 0001
:0001.0170      8D9EEAFC        lea bx, [bp+FCEA];  <======Here is the string!
:0001.0174      03D8            add bx, ax; <====== Calculates the right position..
:0001.0176      C60700          mov byte ptr [bx], 00; <= and puts there the current 

Tricky? You should always be aware, when you see such massive byte - constant loads 
in a target! 

We'll have to find a place for our jump before the file is created AND calculate the offset.

BTW, [bp+FCEA] is indeed [bp-0321] - have you made your homeworks? - but it is still 
not the final address of "CLOCK.TIM". When the file is about to be created it needs 
three more letters (guess which) for the INT 21 call (yes, this target uses DOS-
fashion calls to manipulate this file!), so we have to search for [bp-0321-3] = 
[bp-031F], which looks like [bp+FCDE] in our disassembly.

We find it at : 
:0001.03A2      FFB6E0FC        push word ptr [bp+FCE0]
:0001.03A6      FFB6DEFC        push word ptr [bp+FCDE];  <======= HERE!
:0001.03AA      9AD8005403      call 0007.00D8

So our location should be the 0001.03A2.

There is still another problem. The program creates one more file in its directory, 
called Temp.tst. It is not relevant to our work at all; it serves to check whether 
the program resides on a CD or on the hard disk, but when we place the jump on the 
above address, this file is automaticaly erased otherwise - remains too.
So we make a final Text Find with: 

mov ax, 0026 

where 0026 is the offset of the string "\Temp.tst" in the data segment (DataHex!) 
:0014.0020 43 3A 00 43 3A 00 5C 54  C:.C:.\T <==== HERE!
:0014.0028 45 4D 50 2E 54 53 54 00  EMP.TST.
:0014.0030 54 45 4D 50 2E 54 53 54  TEMP.TST
What comes at last is what we needed: 
:0001.02A9      B82600  mov ax, 0026; <==== HERE COMES the JUMP!!!
:0001.02AC      8CDA    mov dx, ds
:0001.02AE      52      push dx
Now the offset:  In the Windos world most of the jumps are compiled as E9h, which is 
a relative jump with a 16b offset, which means it needs 3 bytes in the binary. The 
offset then is calculated as follows: 

offset = (jump_destination - jump_address) - 3 
In our case: 

offset = (0B74 - 02A9) - 3 = 08C8h 

But it must be compiled as: E9 C8 08 because of the weird way the Intel processors 
store data in memory - the LSB (Low Significant Byte) in the lower addres, the MSB 
(Most Significant Byte) in the higher. 

Now we can crack: Open the program in your HexEditor, go to the offset 000012A9h 
(you've seen it on the task bar of Wdasm!) in Segment 0001 and 

change:         B82600  mov ax, 0026
to:             E9C808  jmp 0B74
We did it! 

As a nice side effect from this, the "entry" nag screen disappears too! But the 
final one and the editor with the order form still remains. You have already gained 
the knowledge nessessary to do this last crack, but there is still something I want 
to show you. 

We start as usual at the beginning of the W32Dasm listing, looking for the dialog 
named "Order info" - this is the text presented in the window's caption of the 
dialog. Here is it: 
Name: DialogID_0107, # of Controls=002, Caption:"Order Info"
 001 - ControlID:0001, Control Class:"" Control Text:"OK"
 002 - ControlID:00D9, Control Class:"" Control Text:""

Now press the DlgRef button, find the one with ID 0107 and doubleclick it. Only one 
reference in our listing: 
:0002.5F92      C8020000        enter 0002, 00
:0002.5F96      56              push si
:0002.5F97      57              push di

* Possible Reference to Dialog: DialogID_0107 
:0002.5F98      680701          push 0107; <============ HERE!
:0002.5F9B      6A00            push 0000
:0002.5F9D      6A00            push 0000
:0002.5F9F      FF7608          push word ptr [bp+08]
:0002.5FA2      FF7606          push word ptr [bp+06]
:0002.5FA5      9AD224685F      call 0006.24D2
:0002.5FAA      C45E06          les bx, [bp+06]
:0002.5FAD      26C707889E      mov word ptr es:[bx], 9E88
:0002.5FB2      26C74702865F    mov word ptr es:[bx+02], SEG ADDR of Segment 0007
:0002.5FB8      C45E06          les bx, [bp+06]
:0002.5FBB      8B4610          mov ax, [bp+10]
:0002.5FBE      26894728        mov es:[bx+28], ax
:0002.5FC2      C45E06          les bx, [bp+06]
:0002.5FC5      8B460E          mov ax, [bp+0E]
:0002.5FC8      2689472A        mov es:[bx+2A], ax
:0002.5FCC      8B460A          mov ax, [bp+0A]
:0002.5FCF      8B560C          mov dx, [bp+0C]
:0002.5FD2      C45E06          les bx, [bp+06]
:0002.5FD5      2689472C        mov es:[bx+2C], ax
:0002.5FD9      FF7608          push word ptr [bp+08]
:0002.5FDC      FF7606          push word ptr [bp+06]
:0002.5FDF      9AAA25A85F      call 0006.25AA; <======= WE NEED THIS ONE
:0002.5FE4      8B4606          mov ax, [bp+06]
:0002.5FE7      8B5608          mov dx, [bp+08]
:0002.5FEA      E90000          jmp 5FED
:0002.5FED      5F              pop di
:0002.5FEE      5E              pop si
:0002.5FEF      C9              leave
:0002.5FF0      CA1000          retf 0010

This target uses enter for the procedure frame, but it doesn't realy matter to us. 
Note that retf 0010 suggests, that the procedure (in this case) have recieved 10h 
prameters (inclusive CS:IP!) and at its end it makes clean-up of the stack. 

Now we can easily crack it by placing retf 0010 at 0002.5f92 instead of enter. But 
in this case it won't work! 

You see, somwhere in the body of this function they set up a jump-table for later 
use; when we bypass it, the program crashes when it quits!  An ugly, really ugly 
trick!  So we must look closer. The second call leads us directly to the dialog 
routines - you can try it if you will, I don't want to bother you with more code 
here. Relevant is the fact that we can crack the above: 

change  :0002.5FD9      FF7608  push word ptr [bp+08]
to      :0002.5FD9      E90800  jmp 5FE4

Since the editor starts immediately after that it's a good idea to find the place 
where this function is called from in the hope to eliminate the WINEXEC() too. We do 
a Text Find (backwards) with call 0002.5F92 as retf tells us that it must be a far 
call. What You Find is What You Needed: 
:0001.30A5      9A908F4730      call 0007.8F90
:0001.30AD      FF7606          push word ptr [bp+06]
:0001.30B0      6A01            push 0001
:0001.30B2      6AFF            push FFFF
:0001.30B4      6AFF            push FFFF|
:0001.30B6      6AFF            push FFFF
:0001.30B8      8D8676FF        lea ax, [bp+FF76]
:0001.30BC      8CD2            mov dx, ss
:0001.30BE      52              push dx
:0001.30BF      50              push ax
:0001.30C0      9A925FBA2B      call 0002.5F92; <==== culprit routine
:0001.30C5      B82008          mov ax, 0820;  <======== FROM HERE...
:0001.30C8      8CDA            mov dx, ds
:0001.30CA      52              push dx
:0001.30CB      50              push ax
:0001.30CC      6A01            push 0001
:0001.30CE      9ACD090000      call KERNEL.WINEXEC; <= !
:0001.30D3      C45E06          les bx, [bp+06]; <======= ...INTO HERE !
There are many other calls to the same routine, but this is not interesting coze all 
of them dwell in our bypassed piece of code (from 0b74 to 02a9). We fall back once 
more on our aproach and patch: 

change  :0001.30C5      B82008          mov ax, 0820
to      :0001.30AD      E90B00          jmp 30D3
Now lets try it - and it works fine! 
That was all for this target. Pretty much? 

Techniques of Setting up Break Points in SoftIce

Here are a few techniques for setting up break points on API's such as 

Enter something as your name and the serial number, and before you hit 'OK' button, 
CTRL-D to SoftICE...

Define a simple macro that will display parameters on the stack when a breakpoint 
occurs. We'll call it 'PARAMS': 

macro PARAMS = "dd ss:esp+4"                ;(display in double word format)

Now we can execute the macro simply by typing its name like any other command. But 
we defined it for another purpose... Put a:

bpx GetDlgItemTextA DO "PARAMS"

Return to the program that uses this API and hit 'OK'. SICE will pop up immediately 
at the beginning of the GetDlgItemTextA function and our little macro will dump the 
function's parameters on stack in the data window when the break occurs. Now, 
remember GetDlgItemTextA's API definition: the pointer to the buffer that receives 
the text is the third parameter on stack and therefore the third DWORD on the stack 
(it's the fourth DWORD actually, but we skipped the first one - the return address; 
thus ESP+4). Note that we could make a more usefull macro that could immediately 
dump the contents of the buffer in the data window: 

macro GETTEXT = "db (ss:esp+4)->8"           ;(display in byte format)

So, the breakpoint would look like this: 

bpx GetDlgItemTextA DO "GETTEXT"

What have we done here? We took esp+4 (the address of the first parameter on stack) 
as the base address of the parameter array and using C-like syntax, we reffered to 
the contents of the third DWORD in the array (which starts at offset 8). This third 
dword happens to be the address of the buffer that receives the text from the edit 
box, so the above command 'db' will display the contents of the memory that begins 
at that address. 

You can also use:

DEX [data-window-number [expression]]

data-window-number:  Number from 0 to 3 indicating which Data window to use. This 
                     number displays on the righthand side of the line above the 
                     Data window.

DEX 0 ss:esp

Every time SoftICE pops up, Data window 0 contains the contents of the stack.

Other Tips For WinIce
Here are some functions you should break point in s-ice when cracking varius 
protections .

Note : Case u get a 'symble not defined' message from s-ice , use the EXP
command (EXP = Display Export Symbols) , for example :

>EXP Message
     1817:006E MESSAGEBOXINDIRECT       1817:0013 MESSAGEBOXEX
     1817:0000 MESSAGEBOX               1817:1E6A MESSAGEBEEP
     0137:BFC023C1 MessageBeep
     0137:BFC038D9 MessageBoxA
     0137:BFC02BEC MessageBoxExA
     0137:BFC038F3 MessageBoxExW
     0137:BFC03D71 MessageBoxIndirectA
     0137:BFC01014 MessageBoxIndirectW
     0137:BFC039A4 MessageBoxW
  Hey look at that , there's a MessageBoxIndirectA symble ;)

Since we don't usually enjoy F12'ing our way out of calls, let's just put a BPRW on the 
processes name instead.. (use MOD to see names).. 

BPRW Process_Name ;this causes softice to break when the application's code starts executing again.
Reading/Writing files :

Reading date from INI file :
(The 'A' at the end is for 32 bit program ... don't worry about it , most
 of the programs are 32 bit, and if they're not , use the same function name
 without the 'A' .. Or use EXP command ;)


Registery Access :

DialogBoxes :

MessageBoxes :

Time And Date :

Creating a window (like a Nag) :

CD-Rom :

HelpFul in hunting serials in VB program : HMEMCPY

To test it you can use s-ice too!
use the '?' command (which is a very powerful command .)
where '^' = XOR
      '&' = AND
      '|' = OR
      '!' = NOT

>?  31 ^ 6E

>?  5F ^ 31

>? esi             ;to see the value in esi ..
>? al & 13         ;result of : Value_In_Al AND 13