Cimatron 10 BETA, v10.6, v11.02, v11.10, v12.0 - Tutorial

"This tutorial has been updated for the final time, it follows the evolution of a good protection scheme, a process which is now at an end as Cimatron (in its present form) will not be continued past v11.02. I've personally followed Cimatron since about v8.0 and have gained immeasurably from the time (probably far too much) that I've spent tracing in SoftICE. If you are looking to crack Cimatron for free or are currently using any of the warez versions I strongly urge you to look into the possibility of acquiring a full legal version, the Cimatron developers have invested many hours in their product and deserve at least some reward".

v10.0 BETA - The beginning.
v10.6 - End of the road for HASP.
v11.02 - The KeyCode algorithm and farewell.
v11.10 - Another little addition (ignore the above introduction ;-) ).
v12.0 - Yet more.....???! - Webpage.
RiSE's (i.e. my release) of v10.6 from the 26/11/99.
RiSE's (zip's release) of v11.02 from the 15/07/00.
UCL's HASP Emulator including source code.

Cimatron is very expensive software and its protection seems formidable, the dongle used by the program (a HASP) has never been practical to eliminate by patching the individual components, instead we must emulate the main routine. The program also uses various codes to enable options (this is merely a registry key - the aptly named KeyCode controls module execution, as you'll see in v11.0 this is also non-trivial). We'll start by locating the trademark HASP routine (I'm using my own 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 (provided to developers by Aladdin).

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.

Basic & Dongle Specific Hasp Services

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 + block.    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 also 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 and the byte read from the dongle. 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 taking a look at the maths behind these codes if you really want some work :-).


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 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) return the correct values according to the service. Making space for our own routine won't be too hard (at least that's 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 task is to identify 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 critical 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 pretty sure Par2 is used to verify the dongle ID, but this can be checked using SoftICE. Lets sketch our emulation code.

Start Emulation

: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.
: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 there's 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 occurrences 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, the main hasp() routine appears to be protected somehow from major modifications, anything more than 5 or so bytes and some routine kicks in (evidently it isn't a problem with decryption 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 40

We 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.

Small Addendum - added 08/04/99

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).

Protection Review

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 & 3 checks. In fact I think the lack of service 3 checks are more due to the fact that Cimatron chose a cheaper dongle without enough memory. This is a good protection and in my opinion would be safe from *normal* JNZ/JZ type crackers, for this reason I choose not to release any ready-made patches or my emulator.

Cimatron v10.6

This tutorial marks the end of my current HASP series and is not recommended for beginners as I'll be discussing some fairly heavy reversing. A message to Cimatron developers should you happen to read this, as I know you do :-), this will be the last version of any of your products I will personally demonstrate how to crack, I doubt however you should sleep too easily though, as there will inevitably be someone else prepared to take my place and distribute your rather good software for free.

We've seen in previous HASP tutorials how to locate easily the main HASP routine, the process of cracking most of these was pretty simple, use your HEX editor to locate the string HASPDOSDRV and change the next E9 (JMP) to E8 (CALL), then patch the entry point to this, several F8's below the CALL [EBP+00] which occurs pretty soon after the CreateFileA call to either of the HASP devices. Obviously all your new routine needed to do was action the service code in BH appropriately, I won't describe this again, reach for your haspman.pdf and take a look at the ASM section. If you are serious about HASP cracking then coding or converting UCL's HASP generator into ASM should be a priority, if you are lazy you can e-mail me and I'll send you the 600 or so byte file I use to emulate all MemoHASP functions.

Of course HASP couldn't make it this easy and version 6 saw the manifestation of the 'envelope' which is a PE cryptor which has some pretty good tricks, relying on the dongle to do the decryption. This usually took the form of calling IsHasp() service 1, then HaspCode() service 2 twice, then decrementing the seed code. With known responses (broken in 1998) all you needed to do was patch in the required return codes and dump the decrypted program using your favourite dumper (need I recommend IceDump).

Version 7 of the envelope however sees a marked change. Firstly, HASP began to realise that certain Russian reversers were making money by selling HASP vxd's which were activated using registry keys (see the UCL link at the top of this essay). I'll show you now for the first time how this is done. The HASP Windows 95/98 drivers consist of 2 files, hasp95.vxd & hasp95dl.vxd, as you might already have guessed hasp95.vxd is the static driver and hasp95dl.vxd is dynamically loaded (dl). The dynamic driver is where the Russians focused their attacks :-

JZ loc_C0000000
JZ loc_C0000002
JZ loc_C000000D
JZ loc_C000001A
CMP EAX, 23h <-- W32_DeviceIOControl.
JZ loc_C000009E
CLC <-- Trace out service by clearing CF.

The Control procedure really is nothing more than a switch statement, you've probably seen the same thing a million times in C or languages of that ilk. Of course HASP being good Microsoft approved vxd ID users also call in via the Microsoft approved DeviceIOControl interface. When EAX=23 its time for some action. The structure at ESI is then the key, specifically the value of [ESI+0C]. When this is 2 we have a HASP service to action, when [ESI+0C] is -1/0/1/3/4 other tasks are performed, look for yourselves by loading your HASP target, entering SoftICE and typing 'vxd hasp95dl' and then setting a bpmb on the control procedure address. All the de-HASPers do is rewrite this dispatch routine, to do so they decrypt the HASP parameters (which at this level are XOR'd with AAh and ROR'd with 4), action the service i.e. return the responses they want, perform a VMMCall _RegOpenKey and _RegQueryValueEx if need be and voila, re-encrypt the parameters and trace out the service. I did exactly the same using v3.81 of the HASP drivers in around an hour and now have a fine selection of working software. Most of the Russian guys also stored various simulated dongles contents in the hasp95dl.vxd too.

HASP of course got wise to this and firstly counteracted it by releasing continually updated drivers, this of course meant that the de-HASPers were forever having to update. Realising this couldn't continue indefinitely the HASP people changed tactics and here marks the spot where the v7 envelope takes over. Now, you can start out with any of the Cimatron programs for this tutorial (getting the RiSE release is probably the way to go), most of the main programs are really just loaders for the corresponding DLL's. I chose chkddf.exe/chkddf.dll as they were comparatively small for disassembly (another 31 files will work exactly as this one).

First thing I did, a bpx on CreateFileA for that driver, but not a hope, I then tried lots of other breakpoints (including DeviceIOControl), the only one I could get to work being MessageBoxA, you can now trace it back, but lets try something first. First place a bpmb on the hasp95dl.vxd control procedure and run chkddf.exe, it doesn't fire. In previous versions the HASP driver had been called into via CreateFileA() / DeviceIOControl(), trace this new encrypted object for long enough and you'll realise ring 0 calls are now used to open the driver, crucially however hasp95dl.vxd is still called by installing itself as the new INT 6 (invalid opcode) exception handler, again trace the new object long enough and you'll find that code, this is no different from v3.81 :-

1C003CFC CALL 1C003C40 <-- Here.
1C003D03 MOV DWORD PTR [1C0327F8], EAX
1C003D08 JZ 1C003D1F
1C003D1F PUSH 1C030054 <-- "Protection Device Not Found".

CALL 1C003CFC is where you'll get your first break. A little StringRef intuition perhaps :-

1C003D40 SUB ESP, 100 <--Make some space on the stack.
1C003D48 XOR ESI, ESI <-- Clear ESI.
1C003D4A CALL 1C008D60 <-- Must be something beneath here.
1C003D51 JZ 1C003D65
1C003D53 PUSH 1C03007C <-- "Your version of the HASP driver is too old".

Realise that our problem was that we couldn't find where the envelope opened communications between the HASP and the program, a little intuition tells you it must have done so beneath CALL 1C008D60 else how could it determine that the version was too old.

1C008D60 PUSH 1C033DD0
1C008D65 PUSH 1C033DCC
1C008D6A PUSH 1C033DC8
1C008D6F PUSH 1C033DC4 <-- These of course will be the return addresses for return parameters.
1C008D74 PUSH 0
1C008D76 PUSH 0
1C008D78 PUSH 0
1C008D7A PUSH 0
1C008D7C PUSH 1 <-- Service.
1C008D7E CALL 1C01E000 <-- This is the start of the .data section!.
1C008D89 XOR EAX, EAX <-- Clear EAX.
1C008D8B ADD ESP, 24 <-- Polish that stack.
1C008D8E CMP ECX, FFFFFF99 <-- -103.
1C008D91 SETE AL <-- Flag AL.
1C008D94 RET

Now we are getting somewhere, lots of parameter pushing to CALL 1C01E000 which is really the start of the .data section! and not disassembled at all by W32Dasm. This is where it must be happening. Lets set a debug register breakpoint for this and see how the code proceeds. Well, I'm not going to tell you everything, but what I will say is that tracing this is like swimming through treacle, some of this code is truly crazy and must have been an incredible pain in the neck to write (let alone trace), in the early parts you should try and distinguish between noise and the real intention of the code, there is a lot of junking, something like 2 in 3 instructions don't do anything and prepare for SoftICE scrolling headache, consider the following :-

:1C01E006 PUSHAD <-- Save all registers.
:1C01E007 ADD AL,00    <-- Junk.
:1C01E009 XCHG ESI,ESI <-- Junk.
:1C01E00B MOV AH,AH    <-- Junk.
:1C01E00D CALL 1C0298D2 <-- Real CALL.

:1C0298D4 MOV EDX,[ESP] <-- Real.
:1C0298D7 JA 1C0298D9 <-- Junk.
:1C0298D9 SUB ESP,-04 <-- Set up for function.
:1C0298DC SHLD EBX,ECX,80 <-- Junk.
:1C0298E0 MOV EBP,ESP <-- Set up for function.
:1C0298E2 XCHG BH,BH <-- Junk.
:1C0298E4 ADD EBP,FFFFFFF4 <-- Set up EBP.
:1C0298EA LEA EDI,[EDI] <-- Junk.
:1C0298EC MOV EBX,1F8DFE10 <-- Junk.
:1C0298F1 XCHG BH,BH <-- Junk.

Why am I showing you this?, well this new encrypted object is seriously well coded to obfuscate debugging and there is a lot of leading of the cracker, I saw a lot of code that looked remarkably familiar in the version 6 envelope yet doesn't work how I expected, e.g. :-

:0072F067 PUSH 0072B6C6
:0072F06C CALL 00729D68 <-- haspreg().
:0072F071 MOV EDI,[ESP+3C]
:0072F075 MOV [EDI],EAX <-- Return Par1.
:0072F077 MOV EDI,[ESP+40]
:0072F07B MOV [EDI],EBX <-- Return Par2.
:0072F07D MOV EDI,[ESP+44]
:0072F081 MOV [EDI],ECX <-- Return Par3.
:0072F083 MOV EDI,[ESP+48]
:0072F087 MOV [EDI],EDX <-- Return Par4.
:0072F089 ADD ESP,04 <-- Correct stack.
:0072F08C POPAD <-- Restore registers.

Pushing these notes aside you of course have already realised the obvious. There is simply no need to study the decryption because our return codes are all verified application side (all the parameters are pushed onto the stack to the encrypted object ready to be actioned). Yet we'll see later how this might have caused problems were it not for Cimatron's protection overload on the main DLL (cimit.dll). All we therefore need to do is insert our emulation routine into the start of the .data section and action the service, we know from the earlier snippet that the return addresses are all pushed to the envelope too :-).

I am going to publish here my complete emulation routine 'as is', this can be pasted into the other 31 protected files with your HEX editor (eventually it will also find its way inside cimit.dll but not yet). Of course the astute amongst you may have quickly noticed a problem. When Service 2 is called the only parameter passed through the stack is the seed code (64) and not the developer passwords, hence were this a completely unknown target we would have a problem, fortunately I knew they were 2209/713D from version 10, in fact we could have discovered them from cimit.dll too as you'll see. Hence Cimatron if you are reading this, take note of those 2 errors.

i) you are only ever using 1 seed code (64) and your passwords are known.
ii) recovering your passwords would have been easy as you used the version 6 envelope to protect cimit.dll.

Obviously we can patch in the correct return codes and not really care.

PUSHAD (60) <-- Save all the registers.
CALL $+5 (E8 00 00 00 00) <-- Our emulator.
POP EBP (5D) <-- Get current address.
XOR EAX, EAX (33 C0) <-- Clear EAX.
MOV EAX, [ESP+24] (8B 44 24 24) <-- Service.
CMP AX, 1 (66 3D 01 00) <-- Is it IsHasp().
JNE $+8 (75 08) <-- Check next service.
MOV EDI, [ESP+38] (8B 7C 24 38) <-- Par1 return address.
MOV [EDI], EAX (89 07) <-- HASP found.
POPAD (61) <-- Restore all registers.
RET (C3) <-- // IsHasp().
CMP AX, 2h (66 3D 02 00) <-- Is it HaspCode().
JNE $+2A (75 2A) <-- Check next service.
MOV AX, 3FA1 (66 B8 A1 3F) <-- Return Code 1.
MOV EDI, [ESP+38] (8B 7C 24 38)
MOV [EDI], EAX (89 07)
MOV AX, FF26 (66 B8 26 FF) <-- Return Code 2.
MOV EDI, [ESP+3C] (8B 7C 24 3C)
MOV [EDI], EAX (89 07)
MOV AX, 5EE7 (66 B8 E7 5E) <-- Return Code 3.
MOV EDI, [ESP+40] (8B 7C 24 40)
MOV [EDI], EAX (89 07)
MOV AX, 4955 (66 B8 55 49) <-- Return Code 4.
MOV EDI, [ESP+44] (8B 7C 24 44)
MOV [EDI], EAX (89 07)
POPAD (61)
RET (C3) <-- // HaspCode().
CMP AX, 4Bh (66 3D 4B 00)
JNE $+2D (75 2D) <-- Always fake successful return.
MOV EDI, [ESP+38] (8B 7C 24 38) <-- Get read address.
MOV EAX, [EDI] (8B 07) <-- In EAX.
LEA EDI, [EBP+68] (8D 7D 68) <-- Start of emulated memory (16 bytes).
ADD EDI, EAX (03 F8) <-- Add the address to read.
MOV AL, BYTE PTR [EDI] (8A 07) <-- Place in AL.
MOV EDI, [ESP+3C] (8B 7C 24 3C) <-- Return Code 2.
MOV [EDI], EAX (89 07)
XOR EAX, EAX (33 C0) <-- Clear EAX.
MOV EDI, [ESP+40] (8B 7C 24 40) <-- Return Code 3.
MOV [EDI], EAX (89 07)
POPAD (61)
RET (C3) // ReadByte().
db 16 (emulated memory).
XOR EAX, EAX (33 C0) <-- Clear EAX.
MOV EDI, [ESP+38] (8B 7C 24 38).
MOV [EDI], EAX (89 07).
MOV EDI, [ESP+3C] (8B 7C 24 3C).
MOV [EDI], EAX (89 07).
MOV EDI, [ESP+40] (8B 7C 24 40).
MOV [EDI], EAX (89 07).
MOV EDI, [ESP+44] (8B 7C 24 44).
MOV [EDI], EAX (89 07).
POPAD (61).
RET (C3).

Note that I emulate only 3 services here (1,2 & 4B), if you scan the disassembly listing closely you'll see that many other services are referenced (4, 32, 46, 48, 49 & 4A), yet the code can never be reached, of course with a program like Cimatron and 31 other DLL's to de-protect you might deem the time it takes to disassemble each DLL unacceptable and therefore emulate these unreferenced services too. With service 4B 2 bytes are read, addresses C & D, these are then XOR'd with 8000h and must equal your dongle ID, i.e. dongle ID 0614 requires 8614 => word C = 14 & word D = 86. Fire up your newly patched chkddf.exe, well what do you know, it works :-).

The main DLL in Cimatron is cimit.DLL, this runs the main program and is thus protected to reflect this. Instead of just the version 7 envelope, Cimatron have also applied version 6 too. Instead of spoon feeding this crack to you, I'll only describe how I did it, the implementation is left to you. Firstly the v6 envelope has to be located, you can do that with the usual bpx on FreeEnvironmentStringsA, the program also checks for Cimagrafi's dongle so watch those passwords :-). I elected to do some redirection here, you can find the JMP HASPDOSDRV easily enough and redirect the JMP to CALL your own routine - this means you must find where the code for this JMP is decrypted (easy with bpr).

The v7 envelope however is decrypted by the v6 envelope which means you can't just paste your emulator routine at the start of the .data section, you've got to find where its decrypted (bpr again) - I warn you this will test your patience as there is a lot of copying 'to and from' to contend with. I didn't think it was practical to work out all the decrypted bytes required to generate my complete emulation routine (which for cimit.DLL reaches 672 bytes), therefore I decrypted only a long JMP to another routine which I placed in the free space at the end of .reloc, remember we don't actually care if the v7 envelope is decrypted as junk, we aren't ever going to call there. In the distributed version of this crack I had to emulate service 3 and did a very clumsy job by modifying the PE header characteristics so I could write a JMP over a JNZ (not aesthetically pleasing).

Those of you who use the RE_ENGE option need a registration number to be able to save properly. The released version of Cimatron patchs the DLL for you, yet I went the extra mile and forced 00061E21 as a valid code. That's it really, no ready made crack for losers here, go and do some work for yourselves :-).

Cimatron v11.02

My final discussion of v11.02 of Cimatron will not damage the developers, a ready made crack already exists for the losers (much to my personal disappointment), I also won't discuss the HASP protection a great deal, for all intense purposes it is the same as per v10.6 with the same vulnerabilities, I understand Cimatron were probably reluctant to annoy registered users by changing the dongle, those of you looking for some neat tricks should trace through the new encrypted HASP object, theres some really good stuff going on there.

The Cimatron set of modules have always been protected by a KeyCode, this controls the features you are licensed to use, the v11.02 release does not include a valid KeyCode, instead the check is crudely patched, heres that code :-

:1C001A19 PUSH ESI <-- 0.
:1C001A1A PUSH EAX <-- dd Result Structure.
:1C001A1B CALL 1C005860 <-- AND out checks.
:1C001A20 ADD ESP,08 <-- Tidy stack.
:1C001A23 TEST EAX,EAX <-- EAX=0 ...
:1C001A25 JZ 1C001A2D <-- & feature isn't enabled.
:1C001A27 MOV [EDI],ESI <-- Copy ESI to array.
:1C001A29 INC EBX
:1C001A2A ADD EDI,04 <-- Shift array.
:1C001A2D INC ESI <-- Just a simple counter.
:1C001A2E CMP ESI, 80 <-- Loop.
:1C001A34 JL 1C001A15

The result structure is 16 bytes in length, each dword controls 0x20 array entries, yet the program only seems to write results to the first 2 dword's, this is in fact all that is needed to obtain a license for all the modules, although one can patch 1C005860 to always return EAX=1 this isn't strictly necessary or correct, although it seems to make not the blindest bit of difference :-). The format of the KeyCode is an integer comma delimited list, its probably declared as a structure, a basic check ensures you must have at least 6 integers. Each member of the KeyCode is passed to atol() (I don't recall if it actually was atol() but the functionality is the same), and then placed in what I've termed as the KeyCode structure :-

struct Cimatron_11_KeyCodes {
  int KeyCode_1
  int KeyCode_2
  int KeyCode_3
  int KeyCode_4
  int KeyCode_5
  int KeyCode_6
} Cimatron_11_KeyCodes, * EAX;

Understanding the components of the entire scheme is critical to reversing it, you won't stand a chance trying to break the individual constituents, the protection by the way resides inside getcod.dll, herewith its make up :-

CALL 1C0034A0 (named Lester).
CALL 1C003110 (named nicki), you should probably trace 1C0030C0 just before too.
CALL 1C0033F0 (named SiNs).
CALL 1C003370 (named spazz).

bool Lester() initialises the KeyCode structure and firstly XOR's its members against each other, a crude loop divides some of these members using primes from the table (7, 0Bh & 13h. note that 7+0Bh=13h-1), remainder checks are performed and results are copied back into the structure, at the end Lester() says 1 for good guy, 0 for bad, he also passes back a new KeyCode structure. bool nicki() is also an interesting function, he uses the dongle ID to construct a table of values, only some of which will be important to spazz(), for simplicity we will try and assume that nicki() is just a black box function i.e. one only needs know its output rather than its make up.

bool SiNs() is a short function that performs a critical check using 2 table values from nicki(). Finally int spazz() is called in a loop twice, each time he returns an integer which is stored away and controls module availability (this is achieved via a long loop which AND's these return values, see the module code above), you'll soon realise for all options that spazz() must give us -1 (FFFFFFFFh) for everything to be enabled, spazz() uses 2 members of the KeyCode structure each time (2nd & 3rd / 5th & 6th).

With 6 KeyCodes to find and 4 functions it isn't the slightest bit practical to start bruteforcing keys. The first thing we do is reverse the inputs required by spazz(), i.e. the required KeyCode structure entries, this is actually quite easy if you identify what are the constant values from the dongle ID (I'm using 0614 btw), 4 simple equations are the result and our entries can be deduced :-

2nd entry - 0x8E8 / 7 = 1 --> 2nd entry = 0x8EF
3rd entry - 0x28A - 2 = 10000 --> 3rd entry = 0x1028C
5th entry - 0xF00 / 7 = 1 --> 5th entry = 0xF07
6th entry - 0x18A - 2 = 10000 --> 6th entry = 0x1018C

SiNs() takes the 2nd & 3rd entries of the KeyCode structure and divides them by 0x8E8, the divisors are stored and added together, the 1st entry has 0x125A and the sum of the remainders subtracted, the result must be 0, as the 2nd & 3rd entries are known quantities we can deduce the 1st (0x12A5). This leads us finally back to Lester() and the following 6 equations :-

int mod_i = 0;
int mod_keytype = 4; // Complete System

i). (KeyCode_2 ^ KeyCode_1) / 7 - mod_i = 0x12A5.
ii). (KeyCode_3 ^ KeyCode_2) / 0xB - mod_keytype = 0x8EF.
iii). (KeyCode_4 ^ KeyCode_3) / 0x13 - (mod_i + mod_keytype) = 0x1028C.
iv). (KeyCode_5 ^ KeyCode_4) / 7 gives mod_i.
v). (KeyCode_6 ^ KeyCode_5) / 0xB - mod_keytype = 0xF07.
vi). (KeyCode_6 / 0x13) gives (mod_i + mod_keytype).

Given all these equations, solving them looks as if its easy but believe me it isn't, go ahead and try it for yourselves, but before you do let me explain the notation a little more, mod_i is a variable modulus (obvious its range is 0-6, because equations i). & iv). divide by 7), mod_keytype controls the Modules description, I define 4 because this equates to 'Complete System' which I think is the best one, I don't think it matters too much if you choose an arbitrary starting value for KeyCode_1, (I chose 100000h).

As you may see or will realise if you try, you have to make an assumption as to the value of mod_i, you can then obviously work through systematically (using basic inference) solving each of the equations, (recommended order is i)., ii)., iii)., vi)., v). & iv).) but theres a snag if your unlucky with your choice of mod_i, you have only a 1 in 7 chance of picking the right one that will solve correctly the entire system of equations. So you've got a choice, do the maths on a nice sheet of A4 or write a program to test the 7 values of mod_i for you.

You guessed it, I wrote a program to do it for me, the source code can be found in the Key Generator archive, a screenshot of the output is shown below, one needs to run the program 7 times (trying each remainder), when one is valid you can SoftICE to the valid KeyCodes.

Cimatron 11 KeyCode Validator

Oh yes, did I forget?, the valid KeyCode, well try '1048576,1082019,1106134,250322,1293177,1252868' :-). An interesting scheme me thinks.

Cimatron v11.10 - Addition by CrackZ

I keep coming back to Cimatron, every version seems to pull a few new surprises :-), this will be the last time though I promise. v11.10 marks no real change in protection strategy however I will use this opportunity to discuss several other ideas and correct one or two theories. In v10.6 I said incorrectly that the first dongle checked out by Cimatron was Cimagrafi's, it certainly isn't, the 0xDE9/0x3D4C dongle is in fact a MemoHASP-1 supported by Cimatron (in fact its used for permanent license holders), the difficulty of emulating its presence (which would seem preferable) is due to the way the HASP object obfuscates the parameters pushed for service 32 (or ReadBlock), this means we can't trivially recover the required parameters directly off the stack (although we can recover them if we trace low enough into the new object).

In v10.6 I suggested that you decrypt a long JMP at the start of the emulator code to some new code, as it turns out the envelope object in 11.10 is only decrypted using a trite XOR, crucially the key is not shifted each time (I think I just assumed in v10.6 it was), it is perfectly safe therefore to use a tool like IceDump to load in your new code allow the decryptor to proceed and dump the correct encrypted bytes for your emulator, then simply paste them over in your hex editor, no more need to find any 00h caves.

v11.10 also tells me that Cimatron have definitely been reading this page :-), the dongle ID 0614 and the KeyCode you'll find above have been blacklisted, although the scheme has not intrinsically changed. The KeyCode itself still appears to be valid (apart from in the main program module). I didn't investigate the blacklisting a great deal, I'm pretty sure it is simplistic, since I used my source code above and generated a new KeyCode for a different dongle ID and the main program happily accepted it. Several of the other options, (MoldBase3D and RE_ENGE) require codes for activation, the protections for these can be found inside interact.dll & re_enge.dll, the latter has only 2 functions which one can rip and fix up from IDA in less than 1/2 an hour (the source code to the crude brute forcer I have added to the archive, it took under 5 seconds).

Finally, in v10.6 I suggested that you don't emulate the other TimeHASP specific services (e.g. GetDate() ) since I couldn't see how they would be called, this isn't actually the case for the main module (which I didn't ever disassemble since its about 40Mb's uncompressed), the other TimeHASP specific services will be called if your KeyCode isn't permanent (of course with my discussing of the algorithm in v11.0 there is simply no need for you to research using a temporary key ;-) ).

Cimatron v12.0 - And so it continues.....

In v11.0 I examined the KeyCode algorithm which Cimatron use to control module access (a crude brute force resulted in being able to generate correct keys), I speculated that there was possibly some sort of algorithmic hole, where as our keycodes generated 2 results there appeared to be room and loop provision for 4, indeed this is the case if you stick with the assumption I had made, after talking with many registered users it seemed to me Cimatron only ever issued KeyCodes with 6 or so entries, for some reason (maybe a previous version) I had it in my head that any more than 8 was bad and that the final 2 of 6 entries controlled some sort of expiry (this isn't a verified theory btw).

I realised looking closely at v12.0 that the above probably was the case when Cimatron had less than 64 module options (recall each DWORD result generated controls a maximum of 32 options) but definitely isn't the case any longer since there are now 80+ modules and to fully license a system those entries would have to be filled in. The answer is obvious, the scheme was designed with this future proofing in mind, a few changes in the source code open up the remaining 2 results and in v12.0 and maybe before, KeyCodes can now consist of up to 13 entries. In v11.0 I said Lester() was of bool return type, this was an assumption I made on the basis that had I been writing a function that said either yes or no I'd have used bool (it probably doesn't produce any meaningful optimisation although you are always encouraged in any programming language to use the smallest type you can).

In reality it doesn't make a great deal of difference whether its an int or bool since the sequence of opcodes which might test the result would be the same (in length and probably cycles). As far as I can tell every part of Cimatron itself is written in VC++, consider if you the following quick piece of C/C++ code :

#include <stdio.h>

// Prototypes
bool func1(void);
int func2(void);

int main(void)
  return 0;

bool func1(void)
  return true; // bool return type

int func2(void)
  return 1; // int return type

A quick examination in W32Dasm confirms that VC++ compiles these return types as one might expect, func1() gives mov al, 1 and func2() mov eax, 1, for free we also get some aligning 0xCC bytes and preservation of ebp/ebx/esi/edi in a function that doesn't ever alter them, thus every function declared by Cimatron for this scheme we can pretty much assume as return type int. In v12.0 Lester() is sub_1C0034D0, he has quite fittingly space for 3 locals (sub esp, 1ch at the function start and 4 pushes of the non-scratch registers leaves 3 locals ;-) ). With 12 key members, we proceed through the XOR'ing loop of entries.

2nd ^ 1st - result placed in 1st
3rd ^ 2nd - result placed in 2nd
4th ^ 3rd - result placed in 3rd
12th ^ 11th - result placed in 11th

One can easily see that only the 12th entry of the keycode structure remains unaltered by this process. The algorithm proceeds as per v11.0, with 2 of the 3 locals holding critical modulus results, the third is a simple additive check of these two moduli, the first controls package type (4 for COMPLETE SYSTEM), the second system type (by all accounts CIMIT-MDN (1)) looks to be the best. As before the dongle ID is used to generate a table of values which are used in the calculation of the option DWORD (I only found this out later when I produced a full key generator). In v11.0 I gave a working keycode and dongle ID to use and thus I was curious why this combination failed to pass the maths in v12.0.

Well here's the deal, it still does pass the ^ maths and the modulus checks, however the checksum values calculated for v11.0 (those that enabled me to deduce the required keycode structure entries) no longer hold, this looks to me like a deliberate change by the developers and one they probably make every version, there is however a curious piece of code in the checksumming function :

:1C00348D CMP EAX,[1C02DDE8] ; 0xD3, signature check for v11.0 Keycode maybe ;-).
:1C003493 JNZ 1C0034C4 ; Out you go.

I thought at first this was just a 'lets check if we've got an old key' routine, left in there for straggling upgrades perhaps (maybe some sort of version control?), however the checksum function will always fail the return check, so why this explicit compare against the v11.0 residue?, if its an invalid keycode regardless of the checksum why check for it explicitly?. As it turns out even patching this won't give you a complete system, the routine which derives the constants from the dongle ID in v11.0 is not the same as that in v12.0 and its this that puts pay to easy ways to get a permanent key. In reality of course the casual lame scene cracker is not going to bother generating codes, he's going to patch away the function return checks and patch the AND checks on the option DWORD (too easy by far). Not to worry though, you can always buy a very secure HASP 4 and enjoy yet more security via obscurity ;-).

Until we meet again ;-).

greenball.gif (835 bytes) Dongles Return to Main Index

© 1998, 1999, 2000, 2001 CrackZ. 15/16th March 1999, 2nd April 1999, 29th August 2000, 10th August 2001.