_Nordic_'s CrackMe #1
Ripping code to create a Keygen
Written by Sphinx


Introduction


Ok. Even if the sub-title is about "ripping code to create a keygen", it's still (very) important to actually understand
the code itself, else, you wouldn't be able to rip, if you don't understand what you're going to rip in the first place :) And
that's one reason why I like coding in ASM :) So to be able to rip, you must also be able to code in, well, ASM ;)



Tools required


SoftICE (I used v3.25)
TASM (I used v4.1)

Target's URL

The Millenium Group (TMG) Official Website



Essay


Ok. This is his first CrackMe and has been described as "ASM byte twisting.", so I chose this ;) And it really has ASM
byte twisting in it. Anyway, let's snoop! Though there's nothing really fruitful doing that :) So let's go to the next stage...

- Run it (yep!). Ahhh, we're "oblige" to enter a serial. So I entered the faves: Sphinx as name and 12345 as serial.
  Then hit "Check". And geeesh! We're wrong! And after being acknowledged, it immediately exits (sux!). So run
  anew and re-enter the reginfos. Hmmm, but breakpointing the common serial-related APIs doesn't seem to help much,
  even the useful "hmemcpy." So we'll try another method as described by ^pain^. Ok. Since it displayed a dialog box
  (for the error message), we'll try this:

- In SoftICE, type: bpx MessageBoxA [enter]

- Mash F5 (or Ctrl-D) to go back to our prey (sadista noh? hehe)

- Hit "Check". Then break!. Hit F12 then the nag is drawn. But we don't need that, so click "Ok". Then break!

After cracking some apps, you'll know the usual way of arriving to good/bad message boxes, and this is NO exception!
Whatever, we could use this bad message to our advantage. Anyway, let's scroll up a few lines until we find (or feel :)
a good spot to stop. Here, we'll backtrace to the start of this routine. Anyway, here's a snippet:

You see: (as exported from SoftICE with "CODE OFF")
 ...(some crap)...
 0137:0040115A LEAVE
 0137:0040115B RET 0010

Hmmm, seems to be the end of another routine, so the next line after this (probably) is the one! So breakpoint the NEXT
line after that RET. Use your mouse or type: bpx cs:40115E in SoftICE. Ok. Now run anew coz the program auto-exits when wrong (really sux!). Enter the reginfos again, the hit "Ok". Then we'll break here! Hmmm, seems to be correct thus
far :) Ok. Then we start tracing here...

 0137:0040115E PUSH 00402128 ; buffer for username
 0137:00401163 PUSH 21 ; 21-1 = 20h (or 32 in dec). Yep, the max_length
 0137:00401165 PUSH 0D
 0137:00401167 PUSH DWORD PTR [00402110]
 0137:0040116D CALL USER32!SendMessageA ; get name; length in eax
 Heh, so that's why the common APIs didn't work :P


 0137:00401172 CMP EAX,04 ; is eax < 4?
 0137:00401175 JL 0040133C ; yes? jump, else...
 So now we know that name should be >= 4 chars AND <= 32 chars.


 0137:0040117B MOV [00402124],EAX ; save namelength to [00402124]
 0137:00401180 MOV BYTE PTR [0040211C],00 ; initial value of [0040211C]
 0137:00401187 MOV EDI,004021AC ; after, edi = new (empty) buffer offset (let's call it as "table" :)
 0137:0040118C MOV EDX,00000001 ; initial value
 0137:00401191 MOV ECX,[00402124] ; ecx = 6 (the length, see :0040117B)
 0137:00401197 MOV ESI,00402128 ; after, esi = name offset
 0137:0040119C LODSB ; load a byte from DS:ESI (the name), so first, al = 53 ("S")
 0137:0040119D SHL EAX,1 ; eax = eax << 1, so eax = A6.

Interruption:
To those new to the "shl" instruction. That "eax = eax << 1" is just a shorthand way of saying "eax = eax * 2".
But why shortcut it? coz if you don't, in ASM (of course :P), you would normally type this similar to:
 mov eax, 53h
 mov ecx, 2
 imul ecx

And eax would also result to A6 (as above). See? A 3-liner could be just a 1-liner? Tip! Tip! Ok. But why multiplying
it with just 2, coz this works as: 2^1 (that 1 is from that "shl eax, 1"), so the result is 2 (btw, that "^" is not for our usual
xoring but means "to the power of"). And "shr" is for division :) Nuff said!

Anyway, let's continue...

 0137:0040119F SUB EAX,01 ; eax = A6 - 1, so eax = A5 (nope! no shorthand for this one ;-)
 0137:004011A2 XOR [EDI],AL ; byte at [EDI] get xored with al eh?
 ie.

 [EDI] = [EDI] xor al (note that first, the byte at [EDI] is 0!)
           = 0 xor A5
[EDI] = A5

 0137:004011A4 XOR [EDI],DL ; now xor it with dl ha?
 ie.

 [EDI] = [EDI] xor al
           = A5 xor 1 (see :0040118C)
 [EDI] = A4

Ok. One of the important part's coming....

 0137:004011A6 CMP BYTE PTR [EDI],21 ; is byte at [EDI] (here, A4) < 21?
 0137:004011A9 JB 004011B0 ; yes? jump, else... here, NO!
 0137:004011AB CMP BYTE PTR [EDI],7A ; is it < 7A then?
 0137:004011AE JB 004011B5 ; yes? jump, else... here, NO!
 0137:004011B0 ADD BYTE PTR [EDI],1D ; then add 1D to it then
 ie.

 [EDI] = [EDI] + 1D
           = A4 + 1D (see :0040118C)
 [EDI] = C1

 0137:004011B3 JMP 004011A6 ; jump back and test it again

Ok. Here's how it executes:
- A4 + 1D = C1
- is C1 < 21? Nope. < 7B? Nope. So add 1D.
- is DE < 21? Nope. < 7B? Nope. So add 1D.
- is FB < 21? Nope. < 7B? Nope. So add 1D.
  Ok. FB + 1D = 118 in "real-life". But we're dealing with just a byte here, so it would, like saying "truncate" it to fit a
  byte's size. Anyway here, the sum is 18 only (see? truncated from 118 ;-) So nuff of that. Continuing...
- is 18 < 21? Yeah! So jump and add another 1D (see where it jumps)
- is 35 < 21? Nope. < 7B? Sure, so jump...

 0137:004011B5 ADD EDX,ECX ;... HERE! Ahem, so edx = edx + ecx
 ie.

 edx = edx + ecx
       = 1 + 6 (the 1 from :0040118C and the 6 the namelength)
 edx = guess?

 0137:004011B7 ADD [EDI+01],DL ; hmmm, add dl to the next byte of table eh?
 ie.

 [EDI+1] = [EDI+1] + DL
               = 0 + 7 (it's 0 coz it points to the next byte of table1 and table1 initially had 0's)
 [EDI+1] = what?

0137:004011BA ADD [EDI+01],AL ; heh, add al to that 7
 ie.

 [EDI+1] = [EDI+1] + DL
               = 7 + A5 (see :0040119F)
 [EDI+1] = AC

 0137:004011BD INC DWORD PTR [0040211C] ; inc master counter
 0137:004011C3 INC EDI ; inc table pointer
 0137:004011C4 LOOP 0040119C ; loop back while ecx != 0 (ecx acts as the sub-counter)

So loop back until all the name_chars have been processed. And the table will also be updated using the above then...

 0137:004011C6 CMP DWORD PTR [0040211C],000000FF ; is master_counter <= FF?
 0137:004011D0 JBE 00401191 ; yes? jump, else...

So repeat it ALL again while master_counter is <= FF! And notice where it jumps to? Yep, to :00401191 and look at the instructions there. Hey! ecx is the namelength again and esi points to the username again. So it keeps processing the name again and again until the master_counter's > FF. Anyway, let's continue shall we?

 0137:004011D2 MOV ECX,00000008 ; ecx = 8 (the max realcode length. Hint!)
 0137:004011D7 MOV [00402120],ECX ; save it first
 0137:004011DD MOV ESI,004021AC ; esi = table offset
 0137:004011E2 MOV EBX,ESI ; ebx too (for the "xlat" instruction below)
 0137:004011E4 MOV EDX,004022B0 ; edx = realcode offset (this is where to sniff :)
 0137:004011E9 XOR EAX,EAX ; clear eax first to be sure no errors will occur
 0137:004011EB LODSB ; load a byte from DS:(E)SI (the table), so first, al=35
 0137:004011EC XLAT ; Ok. This is what determines the realcode chars....

Ok. Time to explain a little that "xlat" instruction noh? Ok. Before using xlat, we make sure (E)BX is pointed to the
translation table (like the above did at :004011E2). Then the program used "lodsb" to load a byte from the table to al
(here, al=35). And that 35 will be used as the index in the table then return to al the byte pointed to by that 35! Simple eh? And from my table, the byte at 35 is a 21h, so al = 21 after calling xlat. Yep, that's it!

 0137:004011ED MOV [EDX],AL ; so first realcode char is a 21 ("!")
 0137:004011EF INC EDX ; inc realcode pointer
 0137:004011F0 LOOP 004011E9 ; loop again while ecx != 0
 Here, the initial value of ecx was 8 remember? So that's why the realcode has 8 chars! Hmph!

Anyway, let's sniff the serial and mine's !.(!.<&(.

Yeah, yeah. I know I said that it's important to know how the program compares our serials like in Softy 1.07a neh?
And like Softy, this also has a "special" way how the program compares the serials (and even how it checks the serial
length!) Really amusing actually :) But checking/comparing them is like calculating again (btw, which is kinda long!). So
we'll skip this part :) Oh yeah, the good/bad messages were all encrypted. Maybe to prevent being seen in SDR ;-)

Hey! Have you noticed this? Remember the first badboy message? Ok. Now try registering with a valid serial. Ok.
After the goodboy message, try entering it again. Whoa! It's wrong! That's coz the program doesn't reset the table again
back to 0's. It uses the last one as the base. And notice the second badboy message is different from the last one. Ok.
Try entering that new serial and lo! The (presumably) second goodboy message is STILL encrypted! Hmmm... strange?
Very strange indeed ;-) So now we have the infos to build a keygen. And here's mine coded for a 16bit-COM phile!

Code Segment
Assume CS:Code,DS:Code,ES:Code,SS:Code
Org 100h

Start:
   mov ah, 9
   lea dx, [nl]
   int 21h
   lea dx, [intro]
   int 21h
   lea dx, [nl] ; aesthetic reasons ;-)
   int 21h
   lea dx, [nl]
   int 21h
   lea dx, [prompt]
   int 21h
   mov ah, 0Ah
   lea dx, [username]
   int 21h
   cmp Username+1, 4 ; length check
   jl no_name

   ; serial calculation #1 (ripped! Look, even the addresses! Hah!)
   lea di, [buf] ; di points to our buffer
   mov dx, 1
_401191:
   xor ax, ax
   mov cl, byte ptr [username+1]
   lea si, [username+2]
_40119C:
   lodsb
   shl ax, 1
   sub ax, 1
   xor byte ptr [di], al
   xor byte ptr [di], dl
_4011A6:
   cmp byte ptr [di], 21h
   jb _4011B0
   cmp byte ptr [di], 7Ah
   jb _4011B5
_4011B0:
   add byte ptr [di], 1Dh
   jmp short _4011A6
_4011B5:
   add dx, cx
   add byte ptr [di+1], dl
   add byte ptr [di+1], al
   inc word ptr [counter]
   inc di
   loop _40119C
   cmp word ptr [counter], 0FFh
   jbe _401191

   ; display "reg_no" string
   mov ah, 9
   lea dx, [nl]
   int 21h
   lea dx, [reg_no]
   int 21h

   ; serial calculation #2 (also ripped!)
   mov cx, 8
   lea si, [buf]
   mov bx, si
_4011E9:
   xor ax, ax
   lodsb
   xlat ; yep, you guessed it :)
   mov ah, 2 ; instead of making a new buffer,
   mov dl, al ; print it directly to the console
   int 21h
   loop _4011E9
   mov ah, 9
   lea dx, [nl]
   int 21h
   jmp short quit

no_name:
   mov ah, 9
   lea dx, [nl]
   int 21h
   lea dx, [error]
   int 21h
   lea dx, [nl]
   int 21h
quit:
   mov ax, 4C00h
   int 21h

; data starts here
Intro          db "_Nordic_'s Crackme #1 Keygen by Sphinx [04/18/2001]$"
Prompt      db "Enter your name: $"
Reg_no     db "Registration no: $"
Error         db "Error: Name should be >= 4 characters.$"
Username  db 1Ah, 1Bh dup (0)
nl              db 0Dh,0Ah,24h
Counter    dw 0
Buf           db 0FFh dup (0)

Code Ends
End Start

Then run "TASM keygen.asm" then "TLINK /t keygen.obj" to compile to a COM phile! Phiew! Well, that's it!
I just wanted to prove that I understood it's workings (heh!). Yeah, I'm done now, later.

Sphinx [sphinxter@edsamail.com.ph]

Final Notes



My Greetz Goes to:



TRES2000 (for the chance)
^pain^ (for the t3kn33k ;)
Andrea




When ever there is a door,
there is an entrance.
And behind an entrance can no secret hide,
when a cracker takes his knowledge for a ride
                                                                               McCodEMaN



ObDuh

The information in this essay is for educational purpose only!
You are only allow to crack, reverse engineer, modify code and debugg programs that you legaly bought and then for personal use only!!
To ignore this warning is a criminell act and can result in lawful actions!

So please note!
I take no responebility for how you use the information in this essay, i take NO responebility for what might happen to you or your computer!
You use this information on your own risk!!









Essay written by Sphinx ŠTRES2000. All Rights Reserved.