http://www.cimatron.com - Webpage.
Installation version (65Mb's).
Cimatron is very expensive software and its protection when you first look seems formidable, the dongle used by the program has never been practicle to eliminate by patching alone, instead we must emulate the main HASP routine. The program also uses various codes to enable options (this is merely a registry key - the aptly named KeyCode controls module execution). We'll start by locating the trademark HASP routine (I'm using my own trademark program to do this by the way, but it isn't exactly difficult to code your own findhasp.exe :) ). 2 locations were uncovered but the one we'll focus in on first is haspms32.dll.
Fortunately you can disassemble this file easily and locate the 'hasp' export, this is the dongle calling routine which receives and actions the service codes. I focused in on cimit.dll as containing the majority of the checks simply because all Cimatron modules are really loaders for cimit.dll. Cimit.dll also houses the KeyCode check which I won't deal with here even though that is worthy of some discussion. Cimit.dll is a hefty 6.6Mb's and disassembly with W32Dasm was the only option on my PC.
2hrs later and sure enough if you check through the function imports you'll find haspms32.hasp imported (this is good news for us), the number of CALL's however is evidently NOT so great. Some 22 separate checks, many different services but all located fairly close together in address terms. We'll list them out for arguments sake, remember the format of parameter pushing.
=============================================================================== Service Name/No. No. of returned Parameters Other X-reference addresses =============================================================================== IsHasp() - 1 1 status parameter (Par1). 1C53E115, 1C53E1C7 HaspCode() - 2 4 returned parameters. 1C53E164, 1C53E4FA ReadWord() - 3 2 returns (word + status). 1C53E3E9 WriteWord() - 4 1 status parameter. 1C53E288, 1C53E2F1 ReadBlock() - 32 1 status parameter. 1C53E372 SetTime() - 46 1 status parameter. 1C53E8A4 GetTime() - 47 4 returned parameters. 1C53E98B SetDate() - 48 1 status parameter. 1C53E908 GetDate() - 49 4 returned parameters. 1C53EA20 WriteByte() - 4A 1 status parameter. 1C53E5E9, 1C53E66F, 1C53E6C6 1C53EAC7, 1C53EB24. ReadByte() - 4B 2 returns (byte + status). 1C53E73C, 1C53E7C1, 1C53E81B 1C53EB80, 1C53EBDE.
All these services and checks look daunting, 22 checks in all and the Cimatron developers really like reading from and writing to the dongle, they also make use of the real-time clock. Sadly this wonderful API structure has an inherent weakness, reverse engineers can see exactly what the program should return, obviously in the case of Service 2 we'll use UCL's HASP generator to find the real return codes (although we could be in for trouble if other dll's call the service with different seeds), all the parameters however look pretty easy to retrieve. We can load haspms32.dll exports into SoftICE's Loader, this will be useful for locating the initial checks but ultimately we'll want to trace the dll and code our emulator routine there.
Lets bpx for hasp(). Our first break is address 1C53E4B1 (Service 1), it seems as if the DWORD that is checked should be non-zero. After modifying this check in memory Service 2 is called at 1C53E4FA, here we can note our desired parameters :), Password 2 is 713D, Password 1 is 2209 and the seed code is 64 (resulting returned parameters are thus - 3FA1, FF26, 5EE7 & 4955). Cimatron developers evidently aren't stupid though, they don't just check the return codes, instead they manipulate them (only subtraction though) and use the results as flags, this however won't be an issue when we "emulate" these returns inside the main hasp() routine.
A final bpx occurs with a call to Service 4B, this returns an error which is -28 (HASP with the specified password wasn't found), evidently this should be 0. So we can see initially what needs to be done, but thus far we've only found 3 of the services, remember that its not difficult to reverse your checking procedure on each service instance, take a look at Service 2.
:1C53E4FA CALL hasp() Service 2.
:1C53E4FF MOV ECX, DWORD PTR [1D33D7C8] <-- Return code 1 (3FA1).
:1C53E518 MOV EAX, DWORD PTR [1D33D7CC] <-- Return code 2 (FF26).
:1C53E530 MOV EDX, DWORD PTR [1D33D7D0] <-- Return code 3 (5EE7).
:1C53E549 MOV ECX, DWORD PTR [1D33D7D4] <-- Return code 4 (4955).
Cimatron developers actually subtract these values from other memory locations,
the desired result is always 0. With Service 4B we will need to work out
the value of the return byte (we know status Par3 must be 0). Before we do all
of this I'll give you a valid KeyCode for the registry. Actually its worth
just taking a look at the maths behind these codes which are validated firstly
using signed IDIV's.
"25077193,24980256,22023906,14541172,14488979,6305836,6222823,4336014"
Lets return to our HASP emulator. When we start if you bpx for hasp() you'll find calls are made to services 1,2 & 4B (x2). Lets take a look inside haspms32.dll (MEMORISE THIS CODE PATTERN).
:10002E46 CMP BH,32 <-- BH is always the service code.
:10002E50 JB 10002E50 <-- Handle basic services slightly differently.
:10002E56 CALL 10002C64 <-- Communicate with HASP.
After the call to communicate with the HASP our parameters are ready to be returned Par1=AX, Par2=BX, Par3=CX & Par4=DX, so the objective for our emulator is to i) identify the service code, ii) respond accordingly & iii) patch the correct values according to the service. Making space for our own routine won't be too hard (at least thats what I thought first), we can just overwrite the call to hasp() with our emulator CALL (CALL being preferable to JMP because we can RET easily).
Our first problem will evidently be identifying the service, simple really, its always the value of BH. Lets talk a little about the service handling, service 1 won't be a problem, where as service 2 might have been trickier if the protectionist had issued 2 service 2 calls with a different seed code, I think however that this isn't the case. For service 3 we know CX (Par3) will need to be 0 and BX (Par2) must be a word from the dongle, however I couldn't see how service 3 would get called, certainly the creation of a stack frame at 1C53E3B0 doesn't seem to ever be reached, we'll emulate it just in case. Service's 4/32/4A will prove trivial (Par3=0). We'll look at the Time/Date specific services later, service 4B might give us a headache if the return parameters are verified or used as variables.
With Service 4B I'm hoping for some luck, if you think just briefly in high level language terms you'll realise its pretty hard not to give away the value of your expected return code (Par2) if you use it either as a variable or flag, the need to error handle would see to that, obviously there is an issue regarding where any flag might be checked. Looking at each 4B service, both Par2/Par3 always seem to be verified, I'm unsure whether Par2 is actually used as anything other than a flag pointer but this can be verified using SoftICE. Lets sketch our emulation code.
:CMP BH,01 <-- Is it Service 1 (80 FF 01).
:JNZ next_service
:MOV EAX,00000001 <-- EAX (Par1 = 1) - HASP found.
:RET <-- Return from emulator.
:CMP BH,02 <-- Is it Service 2 (80 FF 02).
:JNZ next_service
:MOV EAX, 00003FA1 <-- Par1 (B8 A1 3F 00 00).
:MOV EBX, 0000FF26 <-- Par2 (BB 26 FF 00 00).
:MOV ECX, 00005EE7 <-- Par3 (B9 E7 5E 00 00).
:MOV EDX, 00004955 <-- Par4 (BA 55 49 00 00).
:RET <-- Return from emulator.
:CMP BH,03 <-- Is it Service 3 (80 FF 03).
:JNZ next_service
:XOR ECX,ECX <-- Clear Par3.
:MOV EBX,00000001 <-- I think Par2 should be 1.
:RET
:CMP BH,04 <-- Is it Service 4 (80 FF 04).
:JNZ next_service
.....
I won't complete this, you should dig the idea for the remaining service codes.
We must however check services 46-49, (46 & 48) will both prove trivial, just
set Par3=0 i.e. status good and these checks are dead. The calls to services
47 & 49 however return 4 parameters, Par3 being the only one we can reliably fix.
As this HASP evidently has a real-time clock which we cannot emulate easily
we'll have to figure out which parameters are checked, we could infer service
47's result because service 49 ought to be called (else there would be little
point including it). Lets translate all these parameters into the 16 bytes of
dongle address space - we can establish this by checking our first calls to
hasp() 1 & 2).
Par1 = 1D33D7C8 (AX)
Par2 = 1D33D7CC (BX)
Par3 = 1D33D7D0 (CX)
Par4 = 1D33D7D4 (DX)
This translation shows us that Services 47 & 49 check only Par3 (the status). We can of course for aesthetic purposes only patch in some bogus date values even though these appear never to be verified. I patched all this into my emulator routine but alas theres a screw loose, we run Cimatron and its "No Protection Device", the reason for this is a really sneaky trick by the Cimatron developers, you'll find it at address 1C002F40. A second HASP routine is included inside cimit.dll, it doesn't therefore call our wonderful emulator hence the failure, there are also 4 cross references (fortunately for us we can once again emulate the routine which is exactly the same as haspms32.dll).
This new routine has 4 xrefs, 1C50A276, 1C50A2C1, 1C54FC40 & 1C54FC8B. Services 3, 4B, 3 & 4B respectively, however services 4B will never be called (they are red-herrings), if the program is happy with service 3's result then you will always jump these secondary checks. The presence however of this routine incorporated into the dll is miserable news for us, firstly we'll have to search for any other occurences and secondly we'll have to check all the other major dll's used by the program (even though cimit.dll does most of the work). The instruction 'cmp bh, 32' will be sufficiently narrow a criteria for which to search unless you have your own utility to do so :).
As I suggested, other dll's call our now emulated hasp() as well, catia.dll (22 checks), chkcod.dll (20 checks), chkddf.dll (20 checks) - need I continue?. But (and this is a very big but) there's yet another big screw loose, I added my emulator code and expected everything to work out, but the program crashed most unceremoniously, my first thought :) (lame coding on my part), I bpx'd for the start of the emulation code, after the first 5 opcodes I could see some sort of opcode corruption, here's the real deal reversers, the main hasp() routine is protected somehow from major modifications, anything more than 5 or so bytes and some routine kicks in (evidently it isn't a problem with encryption because the file is completely unencrypted).
This routine gave me major headaches, I depart now from the pedagogy, I tried bpx-ing for file opening API's (BoundsChecker), looking for some sort of CRC/Parity routine but nothing panned out, I tried re-directing the call at a higher level (still corrupted), many more things besides but still I couldn't emulate effectively. In the end I knew several things, firstly it didn't matter where I re-directed calls or where I added code, the routine kicked in, however I could patch 5 bytes and avoid detection, this almost certainly rules out checksumming and CRC, perhaps what we have is a parity checker of sorts, the solution will involve us adding code to the end of the file.
Grab yourselves a copy of a PE Dump Utility, (Dumppe.exe included with Sourcer is perfectly adequate). Dump the contents to a text file using simple DOS output re-direction. e.g.
dumppe haspms32.dll > dumphasp.txt
Consider the following information:
Image Size : 18000 Virtual Size : 568 (from .reloc section). Raw Data Offset : 12200 (from .reloc). Raw Data Size : 600 (from .reloc). Characteristics : 42 00 00 40We quickly check 12200+600=18200h or 75,776 dec (the file size), 600-568=98 bytes of free space in the .reloc section which isn't enough space for our emulator. Instead we'll lengthen the file (I chose 250h), so we add this to our Image Size (18250), Virtual Size (818) and Raw Data Size (850). To perform these changes you'll need to edit the files PE header (search for .reloc using HIEW and you'll find these values in close proximity). We'll also need to change the characteristics from 42 00 00 40 to 42 00 00 E2 (see M$'s PE file documentation to understand this). Finally with our new values we'll need to lengthen the file (12,200+850=12A50h) = 76,368 bytes.
This change will give us enough room to init. a CALL to our emulator and perform the necessary actions, this does indeed work and we can easily RET at the end thus completing the crack (btw the installer makes a call to Service 5 which you may like also to emulate). If you want to look at the emulated haspms32.dll then drop me an e-mail and I'll give you the url.
During the testing of this program it was brought to my attention that even after all this work Cimatron times out after 10 minutes of use, this is because cimit.exe also has an internal hasp() routine independent of the emulator. The services are trivial, (4 & 4A), clearing Par3 (XOR ECX,ECX) will be enough to kill these checks (be sure to trace the code with a well placed INT 3).
I really do commend this protection, the number of checks will force any serious cracker to emulate the main routine, my only slight criticisms are the reliance on checking Par3 (HASP regulars would have noticed that) and the lack of service 2 checks. In fact I think the lack of service 2 checks are more due to the fact that Cimatron chose a cheaper dongle without enough memory to issue more checks. This really is a good protection and in my opinion would be safe from *normal* crackers, for this reason I choose not to release any ready-made patches or my emulator.
Return to Main Index, Dongles.