
_Nordic_'s CrackMe #1
Ripping code to create a Keygen
Written by Sphinx
Introduction |
Tools required |
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 |