Written
by CyberHeg
Jan.
2001
Published
by +Tsehp
Target:
Commotion
3.11 update
www.puffindesigns.com
Tools
used:
IDA
v4.15, Softice v4.05, hex editor (I use hiew and hexworkshop),
Hasp
manual from www.hasp.com and my Hasp flirt signature (from CrackZ's
site).
For
many dongle protections, it is pretty clear that when companies protect
their
products with the latest and greatest commercially available
protection
package
they feel safe because no one will be able to crack it, right? This
is
also a
example of commercial powers playing their tricks. I will discuss
the
HASP
(Aladdin Knowledge Systems) commercial protection, and it's
implementation
in
Commotion 3.11 update.
In
order to understand their scheme you must understand how the HASP key
works.
The
manual is available for download. Without reading this manual learning
hasp is
very
difficult.
First
we install the program on top of the real program (v3.0). I won't
really
go in
details with the serial as I assume that you already have a working
copy
of the
program installed. By running the program we will get a message popup
saying
this program is missing it's HASP key and then the program will start
up
in demo
mode with disabled features.
We load
commotion.exe into IDA. If you want to
save a bit of time, apply my
Hasp
FLIRT signature after disassembling.
From
the manual we see that there is only one Hasp api:
hasp(Service, SeedCode, LptNum, Password1, Password2,
Par1, Par2, Par3,
Par4);
The
Service code determines what function the api executes.
Depending
on this Service code the rest of the parameters can either be zero
or in
use.
This
hasp() call will be located at 00b6c570.
As you
can see on the function reference list it is called from several
places.
At the
address 00b6c70f we notice the main reference to this call:
.text:00B6C6E3 lea eax, [esi+24h]
.text:00B6C6E6 push eax ; Par4
.text:00B6C6E7 lea ecx, [esi+20h]
.text:00B6C6EA push ecx ; Par3
.text:00B6C6EB mov ecx, [esi+14h]
.text:00B6C6EE lea edx, [esi+1Ch]
.text:00B6C6F1 push edx ; Par2
.text:00B6C6F2 mov edx,
[esi+10h]
.text:00B6C6F5 lea eax, [esi+18h]
.text:00B6C6F8 push eax ; Par1
.text:00B6C6F9 mov eax, [esi+0Ch]
.text:00B6C6FC push ecx
; Password2
.text:00B6C6FD mov ecx, [esi+8]
.text:00B6C700 push edx ; Password1
.text:00B6C701 mov edx, [esi+4]
.text:00B6C704 push eax ; LptNum
.text:00B6C705 push ecx ; SeedCode
.text:00B6C706 push edx ; Service
.text:00B6C707 mov [esp+34h+var_4], 0
.text:00B6C70F call _hasp
The
code only pushes the registers so there isn't much else to do then to
make a
map
file
and start using softice.
We
break at the call at address 00B6EC93 which is where we want to place our
emulation.
At the
first break the bh register will be 1. This register always contains
the
Service
number so by looking it up in the manual we will see that this is
the
IsHASP
function getting called. Par1-4 always references to EAX-EDX
regarding
the
return values, so in order to get a correct return value like the manual
states
we need to make EAX = 1.
So this
short piece of code should take care of it:
cmp bh,
1
jne
<next api service>
mov ax,
1
ret
(At the
end of this essay the full emulation is posted.)
Ok,
easily fixed onto next service code. Breaking the same place bh will be
5,
HaspStatus.
Because I had no access to a real dongle, I did not know which
type
it was.
However that does not stop us. We will try to see if it accepts a
Memo-1
key.
Doing so we see in the manual that at the return codes must be Par1(mem
size):
1, Par2(HASP type): 1 and Par3(actual LptNum): 1 for lpt1.
Emulation code:
cmp bh, 5
jne
<next api service>
mov ax,
1
mov bx,
ax
mov cx,
ax
ret
This
code is also self explanatory and testing it shows that the program
accepts
our
choice as it will give us the next service and not stop on this one.
Next
api service will be 2, HaspCode. To use the HaspCode emulation made by
UCL
we need
to find the two passwords and the SeedCode. We will see that the
SeedCode
pushed is 0x82DC and the passwords are Passwd1: 0x63A9 and Passwd2:
0x3EF8
Using the HaspCode emulation we will get: AX: 0xFC1F BX: 0xBFF1 CX:
0x7DED
DX: 0xFDFB.
The
emulation code should be:
cmp bh,
2
jne
<next api service>
cmp ax,
82dc ; SeedCode compare
jne
<next SeedCode>
mov ax,
FC1F
mov bx,
BFF1
mov cx,
7DED
mov dx,
FDFB
ret
Doing
so should make a workable emulation, but it did not.
I
retried a few times but was not able to get it work.
So on with
the manual tracing. Shortly after we reach a call where inside is
this
check:
.text:00B6B070
loc_B6B070:
; DATA XREF:
.rdata:00C7FF90o
.text:00B6B070 cmp dword ptr [ecx+8], 8C6Fh
.text:00B6B077
jnz short loc_B6B09A ; bad code
jump
.text:00B6B079 cmp dword ptr [ecx+0Ch], 0C7CFh
.text:00B6B080 jnz short loc_B6B09A
; bad code
jump
.text:00B6B082 cmp dword ptr [ecx+10h], 73B7h
.text:00B6B089 jnz short loc_B6B09A
; bad code
jump
.text:00B6B08B cmp dword ptr [ecx+14h], 94D9h
.text:00B6B092 jnz short loc_B6B09A
; bad code
jump
.text:00B6B094 mov eax, 1
; good
return
.text:00B6B099 retn
.text:00B6B09A
;
---------------------------------------------------------------------------
.text:00B6B09A
.text:00B6B09A
loc_B6B09A:
; CODE XREF:
sub_B6AEC0+1B7j
.text:00B6B09A ;
sub_B6AEC0+1C0j
...
.text:00B6B09A xor eax, eax
; bad
return
.text:00B6B09C retn
We see
here that this is where the program is failing. Changing our
emulation
to
use
these values makes the checks go good and we get a valid return. My
suspicion
was that this is a HASP 4 dongle but since there are no public
HASP4
HaspCode
emulation available at the time of writing I was not able to
confirm
this.
Well so
far so good. The program will now give us the next service 0x32,
ReadBlock.
Reading
the dongle essay section on CrackZ site will make this a easy task
(thanks
CrackZ).
What is
important to know is what registers hold what:
esi:
length of the read in WORDs
edi:
start address
eax:
pointer to buffer
Our
emulation will then be (a little changed from the one I saw on CrackZ's
site):
cmp bh,
32
jne
<next api service>
push
ebp ; save the value of
ebp
call
$+5 ;
call delta
pop
ebp ; put current
address (IP) in ebp
lea
ebx, [ebp+14] ; get the start of
our dongle memory
pop
ebp ; get original value
of ebp back
mov
ecx, esi ; set up counter
shl
edi, 1 ; multiply by 2
lea
esi, [ebx][edi] ; so we get the
correct address to read from
shr
edi, 1 ; divide by 2
xchg
edi, eax ; buffer pointer into
edi
repe
movsw ; copy with the
length of ecx
xchg edi, eax
; retore eax
ret
DB dup
112(0)
After
this ecx will be zero which matches that Par3 must be zero for
success.
Running
the prog still gives us a error. Since we set the type earlier to a
Memo-1
key we need to make room for the 112 byte long memory array, for that
is
the
size of the array for Memo-1 keys.
Now
it's time to see what the program wants.
We see
when emulating this service that the read address is zero and we
read
0x18 WORD's ahead. To making the solving of the words easy it is a good
idea to
name each byte as 00-18 to clear up the meaning.
Doing
so and following the code after that call we reach here:
.text:00B6B4B8
loc_B6B4B8: ; CODE XREF:
sub_B6B430+67j
.text:00B6B4B8 cmp word ptr [esi+32h], 4450h ; is cell
0x18
4450h ?
.text:00B6B4BE jnz loc_B6B580
; no do bad
return
.text:00B6B4C4 test byte ptr [edi],
8 ; yes it
was
.text:00B6B4C7 jz loc_B6B580
; another
bad
return
.text:00B6B4CD xor ecx, ecx
.text:00B6B4CF mov cx, [esi+30h]
.text:00B6B4D3 mov esi, 64h
.text:00B6B4D8 mov eax, ecx
.text:00B6B4DA cdq
.text:00B6B4DB idiv esi
.text:00B6B4DD mov eax, 51EB851Fh
.text:00B6B4E2 mov [ebp+var_14], edx
.text:00B6B4E5 imul ecx
.text:00B6B4E7 fild [ebp+var_14]
.text:00B6B4EA sar edx, 5
.text:00B6B4ED mov eax, edx
.text:00B6B4EF shr eax, 1Fh
.text:00B6B4F2 add edx, eax
.text:00B6B4F4 mov [ebp+var_14], edx
.text:00B6B4F7 fmul ds:dbl_C57778
.text:00B6B4FD fild [ebp+var_14]
.text:00B6B500 faddp st(1), st
.text:00B6B502 fcomp ds:dbl_C7FF78
.text:00B6B508 fnstsw ax
.text:00B6B50A test ah, 1 ; last compare
.text:00B6B50D jnz short loc_B6B580
; and bad
return
.text:00B6B50F mov al, 1
; good
return
.text:00B6B511 mov ecx, [ebp+var_C]
.text:00B6B514 mov large fs:0, ecx
.text:00B6B51B pop edi
.text:00B6B51C pop esi
.text:00B6B51D pop ebx
.text:00B6B51E mov esp, ebp
.text:00B6B520 pop ebp
.text:00B6B521 retn
.text:00B6B522
;
---------------------------------------------------------------------------
.text:00B6B580
loc_B6B580:
; CODE XREF:
sub_B6B430+8Ej
.text:00B6B580 ;
sub_B6B430+97j ...
.text:00B6B580 mov ecx, [ebp+var_C]
.text:00B6B583 pop edi
.text:00B6B584 pop esi
.text:00B6B585 xor al, al ; bad
return
.text:00B6B587 mov large fs:0, ecx
.text:00B6B58E pop ebx
.text:00B6B58F mov esp, ebp
.text:00B6B591 pop ebp
.text:00B6B592 retn
.text:00B6B592
sub_B6B430 endp
We will
see from this that the first compare tests if the cell at address
0x18
is 4450
if not then go to the bad return code. Then test byte ptr [edi], 8
is
performed.
Edi is pointing at the first cell and the test operation is a
logical
AND
function without returning the result. The conditional jump afterwards
is by
testing
on the zero flag. In my case AND'ing 00 with 08 gives zero and
thereby
the
zeroflag gets set and compare will fail. A valid candidate is 08 itself
since
08 AND 08 will give 1. So a valid word will be 0008. The last compare
uses
cell
0x17 does some math before the real compare. By setting the WORD to
1717
like
suggested before, the compare goes good.
Now the
program starts with no more warnings and nag screens. Checking out
if it
would
break anymore places on the dongle code showed that it would do
another
HaspCode
call when rendering and saving (the two formerly disabled
functions).
However
this was with the same SeedCode as before so no need to do more
emulation.
Conclusion:
This
protection was not hard to defeat even though it was protected by the
infamous
HASP key. The emulation was pretty straight forward where the only
somewhat
hard places was the HaspCode emulator not working and recovering
the
words
of the dongle memory array. Again it shows how commercial protections
companies
fool sofware developers to believe that their protection product
is
safe
and indestructible.
Some
parts of this essay could have been better explained, thats why I
advise
you to
get
hold of this program and try it yourself if you really want to learn the
stuff
you
missed here.
Here is
my full emulation:
.00B6EC93:
80FF01 cmp bh,001 ; IsHASP service?
.00B6EC96:
7505 jne .000B6EC9D; No, try next
service
.00B6EC98:
66B80100 mov ax,00001 ; Yes and a HASP
key is
connected
.00B6EC9C:
C3 retn
.00B6EC9D:
80FF05 cmp bh,005 ; HaspStatus
service?
.00B6ECA0:
750B jne
.000B6ECAD; No, try next
service
.00B6ECA2:
66B80100 mov ax,00001 ; Yes, Memo-1
.00B6ECA6:
668BD8 mov bx,ax ; Memo-1
.00B6ECA9:
668BC8 mov cx,ax ; Lpt1 is used
.00B6ECAC:
C3 retn
.00B6ECAD:
80FF02 cmp bh,002 ; HaspCode
service?
.00B6ECB0:
7517 jne .000B6ECC9; No, try next
service
.00B6ECB2:
663DDC82 cmp
ax,082DC ; Yes, is it
SeedCode
0x82DC?
.00B6ECB6:
7510 jne .000B6ECC8; No
.00B6ECB8:
66B86F8C mov ax,08C6F ; Yes, return the
right
values
.00B6ECBC:
66BBCFC7 mov bx,0C7CF
.00B6ECC0:
66B9B773 mov cx,073B7
.00B6ECC4:
66BAD994 mov dx,094D9
.00B6ECC8:
C3 retn
.00B6ECC9:
80FF32 cmp bh,032 ; ReadBlock
service?
.00B6ECCC:
7518 jne .000B6ECE6; No
.00B6ECCE:
55 push ebp
; Yes do
ReadBlock
emulation
.00B6ECCF:
E800000000 call .000B6ECD4
.00B6ECD4:
5D pop ebp
.00B6ECD5:
8D5D14 lea ebx,[ebp][00014]
.00B6ECD8:
5D pop ebp
.00B6ECD9:
8BCE mov ecx,esi
.00B6ECDB:
D1E7 shl edi,1
.00B6ECDD:
8D343B lea esi,[ebx][edi]
.00B6ECE0:
D1EF shr edi,1
.00B6ECE2:
97 xchg edi,eax
.00B6ECE3:
F366A5 repe movsw
.00B6ECE6:
97 xchg edi,eax
.00B6ECE7:
C3 retn
.00B6ECE8:
DB dup 112 (0)
; Start for the
memory
array