This particular target found its way to me recently and is a good exercise in abstract dongle reversing, you see cracking this target at first looks like a tedious patching affair in the main .exe's, in fact you could almost certainly do it this way, yet reversing is all about understanding and making the best changes you can to a program. FabriCAD is a suite of programs, I'm dealing here with just FabriCAD detailing (fabricad.exe), the documentation advises 3 limitations (all restrictions on the number of elements (Beams, Braces & Columns).
Sniffing the StringRef's we cannot miss the checks (I'll show you just 1 example, there are many others):-
:004CD894 CALL 005B7F36 <-- CALL protection_and_other_unrelated_routines.
:004CD89C CMP DWORD PTR [EAX+0xFC], 0 <-- Check_flag.
:004CD8A3 JZ 004CD914 <-- Jump_past_nag_user.
:004CD8A8 CMP DWORD PTR [EAX+28], 0xA <-- Else_check_columns.
:004CD8AC JL 004CD914 <-- Demo_user_okay.
In all the other checks the code is virtually identical, especially this magical [EAX+0xFC] location. Obviously we now want to take a look at the CALL that sets this flag and now its the dead-end, this CALL is referenced in about 800 places (and thats just my rough estimate without counting them, so what does a lazy cracker do, well he simply searches for the opcodes for this flag (83 B8 FC 00 00 00 00) and replaces them with (C6 8B .. .. .. .. EB) to set the flag to 0 and force the jump immediately afterwards. It probably works but it isn't the best way because there are several references to this flag which don't tally to nice easy StringRef's.
The way to proceed is to pinpoint the code setting this flag, so we'll set a bpm on the single byte location (which turns out to be 62CB14, barring relocation). Now we'll find our real protection :-
:004E70AF CALL RESecure42 . Ord:0001h <-- Returns
:004E70B4 CMP EAX, 7
:004E70B7 JNZ 004E70D7 <-- Jump_good.
:004E70B9 MOV ECX, DWORD PTR [EBP-10] <-- Grab pointer.
:004E70BC ADD ECX, 0xFC <-- Retrieve our magic flag.
:004E70C2 MOV DWORD PTR [EBP-14], ECX <-- Store pointer.
:004E70C5 MOV DWORD PTR [ECX-4], 0 <-- Another_bad_flag.
:004E70CC MOV DWORD PTR [ECX], 1 <-- Move_bad_flag.
At this stage I knew I was dealing with a Hardlock dongle (\\.\HARDLOCK.VxD) and a quick look at the API status codes reveals 7 = no lock connected, lets trace the dll, this next part defies belief.
:xxxx9DE6 PUSH 10019508 <-- "Hasp".
:xxxx9DEB MOV ESI, DWORD PTR [100692B4] <-- MSVCRT._mbscmp
:xxxx9E02 MOV EAX, DWORD PTR [EBP-14] <-- "Demo".
:xxxx9E05 PUSH EAX <-- Onto the stack.
:xxxx9E06 CALL ESI
:xxxx9E0B TEST EAX, EAX
:xxxx9E0D JNZ xxxx9E1E
:xxxx9E0F MOV DWORD PTR [10037ABC], 0 <-- Return flag.
.....and now for more flag setting.
:xxxx9E1E PUSH 10019500 <-- "NetHasp".
:xxxx9E30 MOV DWORD PTR [10037ABC], 1 <-- Need I comment this.
What happens here is a really inefficient checking routine for no less than 8 different protection devices (the others are Glenco, NetGlenco, GlencoNetwork, Sentry, NetSentry & SentinelLM), at the very end "Demo" will be checked and then our return is set to 7. This code therefore needs patching, I elected to use "Hasp", but any of the others will probably do, so long as consistency is enforced. I patched the code appropriately expecting things to work out and then hit a problem, a GPF, there seems to be some sort of tamper checking routine here which detected my patch.
This tamper checking routine is quite bizarre as it will happily let you NOP out both the TEST & deciding JNZ which is exactly what a lazy cracker would do. I'm pedantic however when patching, I really wanted to change the "Demo" to "Hasp" before the CALL ESI and avoid using NOP's altogether.
We can see already from a W32Dasm listing that there is a fair amount of free space at the end of the .reloc section, so the idea is this, we'll insert a JMP to our own small routine, edit the "Demo" to "Hasp" in memory and then CALL ESI (the compare) before jumping back, to do this you'll need to just make a safe PE patch increasing the virtual size of .reloc from 0x2518 to 0x2600 (the sections raw size). This is how the new code looks :-
:xxxx9EE6 far JMP xxxxBB5F (E9 74 1C 07 00)
:xxxxBB5F MOV DWORD PTR [EAX], 0x70736148 (C7 00 48 61 73 70) <-- "Hasp".
:xxxxBB65 CALL ESI (FF D6) <-- Now compare them.
:xxxxBB68 ADD ESP, 8 (83 C4 08) <-- Correct the stack.
:xxxxBB67 far JMP xxxx9EEB (E9 7C E3 F8 FF) <-- Jump back.
Well you might be wondering why I didn't use CALL, the answer is simplicity, by introducing a CALL with the "Demo" pointer already pushed onto the stack I would have been making work. Of course with the benefit of hindsight I now realise that this "Demo" string is a registry key, duh! so this entire last patch was pretty pointless, nonetheless its fun to add your own routines :-). We return to fabricad.exe :-
:004E70D7 CALL RESecure42 . Ord:0003h <-- Returns
:004E70DC TEST EAX, EAX
:004E70DE JNZ 004E7118 <-- Evidently good.
Ord:0003h is also a very interesting export as the value returned by Ord:0001h is used in a switch procedure, as I chose to emulate a Hasp we end up in very familiar territory, the familiar HASP routine with service code checks (xxxx5059). Services 1, 5, 2 (seed codes from a large table), 6, 3 & 50. This is actually a pretty good checking routine, although the checking of words from the dongle is weak. The service 2 (HaspCode) is where the strength lies, the protection selects a seed code which is associated with an index value from 0-0x7A0, then passes this index into a 2k lookup table of the required return codes.
In fact this table causes problems, whether or not they were intended by the protectionist I wouldn't like to say. The problem is this, when I started coding my emulation routine for Service 2 I had two choices, insert my generic ASM HaspCode routine or use the table. Now as my HASPKill program uses the former I was reluctant to expose my ASM Service 2 code (although to be honest it isn't really that hard to do yourself), instead I figured I'd do the following :-
PUSH EDI <-- Save on the stack (note I don't use EAX-EDX as these will hold the return codes).
MOV ESI,[ESP+xx] <-- Get the table index (somewhere offset from the stack).
MOV EDI, start_of_table <-- Start of the table.
ADD EDI,ESI <-- Add the index.
MOV EAX,EBX,ECX,EDX, DWORD PTR [return_codes] <-- Get the correct return codes.
POP ESI <-- Restore.
RET <-- Return from emulation.
This of course looks eminently sensible but there is 1 serious flaw in my proposal above :-), its the relocation problem of course. Look at the checking code from the dll :-
:xxxx76A8 CALL xxxx5033 <-- hasp().
:xxxx76B0 MOV ECX, DWORD PTR [EBP+00] <-- First return code.
:xxxx76B3 CMP DWORD PTR [EDI+10018A5C], ECX <-- Check return code.
:xxxx76B9 JNZ 100076E8
You see that MOV EDI, 10018A5C is valid iff (maths terminology read "if and only if") the dll isn't relocated (but it gets relocated more often than not). The way to get around this is fairly simple, you can use EBP as a relative address to the table, so instead of MOV EDI, start_of_table we use LEA EDI, [EBP+required_offset]. This solves the problem. For completeness I'll mention the service 3 checks, 0,1,2,0xF,0x37 are the positions verified, 0x50DD, 0x0001, 0x0001, 0x0001, 0x0001 as return codes will ensure your happiness :-).
In conclusion, I actually ended up doing more work than I wanted too on this crack, we could have just patched all the various programs using NOP's, untidy but probably effective, this however won't ever be as satisfying as a completely emulated dll. As by the time you are reading this, a crack of some description should be released to the "scene" I recommend you try this yourself. Naturally for aesthetics you could also remove the "DEMO" string by editing the very convenient registry key.
A few weeks after this tutorial was written I had the opportunity to study the suite of programs to which FabriCAD belongs, and guess what, they all use the same HASP dongle, the first word from the dongle (index 0) actually acts as a control switch for the entire suite (its a very good protection using 3 tables and other dongle words), I bruteforced 9F77 as being valid for all of the modules, other positions were also verified by various components using various TEST AH,xx sequences. I was able to recover about 10 other words by having access to this suite, of course it really doesn't make the slightest bit of difference if you are working just with my example above :-).