- Webpage (v4.29 / v5.04 / v5.05 / v5.11 / v5.5rc1 (15th October
Updated 2007 v8.5.6 and above.
Prior to writing this tutorial I'd always made soft patches to Virtual Gibbs, since about v4.23 I'd always been able to change a few bytes and easily crack the dongle protection. This was the case up until about v5.0, after which the developers started using their dongle a little more by controlling functionality (specifically the milling options) with words retrieved from the dongle.
At v5.04 I found it harder to make the aforementioned soft patches just using intuition, a new method was called for and this is where I decided to try and emulate the dongle using the main Sentinel routine (after all I've been doing it with HASP for 2 years or more). Virtual Gibbs uses a SentinelPro of sorts, the API specification I have seems to match up with the code pretty well. A trademark I've seen a lot of in Sentinels is the verification of a packet record which seems always to start with 'Br' (as would be seen in memory).
This usually provides us with a convenient search string and Sentinel as yet don't possess any real PE encryption to hide this code (its definitely a library function). Most of the time I've seen this compiled using ESI, so CMP WORD PTR [ESI], 7242 is usually pretty effective, but on a fast PC just '7242' is sufficiently narrow. From Sentinel's API guide I note that the SuperPro parasite has 8 reserved & 56 general purpose cells, the first 8 (0-7) are reserved (having special meanings) and cannot be re-programmed.
This of course makes sense if you've ever read some of my other Sentinel documents which highlight a 'PUSH 3F' i.e. 63 (the last cell of the dongle) to the Sentinel sproRead() API, each cell is a WORD. My principle idea therefore was to bpx for this "read word" code and check the registers and stack to see what parameters were passed into the function, by simple searching in W32Dasm we find 13 instances of "cmp eax, 7242" and 1 "mov word ptr [eax], 7242" which is obviously not interesting (its most likely some sort of initialisation of the structure).
Some of these '7242' checks are probably innocent, in most Sentinel's I've seen there have usually been 13 instances of this '7242' all compiler pretty close together, however its possible to narrow down most of these using a combination of techniques, by searching the CALL'ing functions we can check the parameters pushed and match them to the API guide, innocent calls to sproInitialize() which must be performed take no parameters and can thus be discarded, I've also found that the usual dongle bpx API's with simple feeling for CALL's returning AX=3 is a another good technique for locating key Sentinel functions.
:006CAEA0 PUSH 00000175 <-- Developer ID.
:006CAEA0 PUSH 008948A0 <-- Push packet record.
:006CAEAA CALL 00787FD1 <-- This can only be sproFindFirstUnit().
This first discovery (which I found by bpio -h 378 rw and F12 work) can only be sproFindFirstUnit(), it can't be any of the other documented API functions because of the parameters pushed, this code also tells us that PUSH 008948A0 is really PUSH packet_record which must be the case for any API calls. Lets refine that search and those bpx's.
:006CAE5E PUSH 00001004 <-- Push Packet_Len.
:006CAE63 PUSH 008948A0 <-- Push packet record.
:006CAE68 CALL 00787D6E <-- sproFormatPacket() initialises default values.
:006CAE81 PUSH 008948A0 <-- Push packet record.
:006CAE86 CALL 00787DBA <-- sproFindNextUnit() check for Sentinel(s).
:006CAEF9 PUSH EAX <-- Pointer to where the word will be returned *.
:006CAEFE ADD ECX, 10
:006CAF01 PUSH ECX <-- Address to be read.
:006CAF02 PUSH 008948A0 <-- Push packet record.
:006CAF07 CALL 007882BE <-- sproRead().
Well, look at this, its all here, we only need now bpx for 6CAE5E and watch it all unfold in front of our very eyes, our first patch will be sproFindFirstUnit(), I changed the MOV AX, 2 to MOV AX, 0 and redirected the JZ to this new code (a 2 byte change) :-
:00787FEF CMP EAX, 7242 <-- Is packet record valid.
:00787FF4 JZ 00787FFA <-- Of course it is.
:00787FFA MOV AX, 0 <-- Now valid.
This change now results in "Hardware key does not match flavor", you've probably already figured that sproRead() must be the culprit, lets have a look there, now look at whats on the stack :-
A0 48 89 00 11 00 00 00 04 FD 3D 01 ^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^ Packet Address Address Record to read to store
You can see by just pressing F5 several times that the address to read changes each time, words 11-17 inclusive are verified, so we'll re-write this code to simulate the presence of our dongle.
:007882E1 JZ 007882E7 (0F 84 00 00 00 00) <-- Packet
:007882E7 PUSH EBP (55)
:007882E7 MOV EAX, [ESP+20] (8B 44 24 20) <-- Get address to read.
:007882EB SHL EAX, 1 (D1 E0) <-- As we are emulating a WORD.
:007882ED CALL $+5 (E8 00 00 00 00)
:007882F3 POP EBP (5D) <-- Set up delta.
:007882F4 LEA EDI, [EBP+1C] (8D 7D 1C) <-- Where the simulated memory will start.
:007882F7 MOVZX EAX, WORD PTR [EAX+EDI] (0F B7 04 38) <-- Retrieve the WORD.
:007882FB MOV EDI, [ESP+24] (8B 7C 24 24) <-- Get address to place dongle WORD.
:007882FF MOV [EDI],AX (66 89 07) <-- Place it.
:00788302 XOR EAX, EAX (33 C0) <-- Success.
:00788304 POP EBP (5D)
:00788305 POP EDI (5F)
:00788306 POP ESI (5E)
:00788307 POP EBX (5B) <-- POP registers from stack as required.
:00788308 LEAVE (C9)
:00788309 RET 0C (C2 0C 00) <-- End.
Now all we need do is bpx for our simulation routine and follow each WORD as it is checked. To enable all of the various options the following values may be of use.
Word Value ---- ----- 11 0400 12 0001 13 00EF 14 0000 15 0002 16 9FF0 17 0000
Note ironically that our simulated memory is using the space previously occupied by the Sentinel protection. You may also like to read some feedback I received from the author of this protection.
A brief addition, in this later version the Sentinel protection remains exactly as that described above and can be defeated in a similar amount of minutes, the only improvement (if one dare to call it that) is the introduction of InstInf_SP.exe to the installation process, this performs the same lamentable checks as the main program, 2 patches instead of 1.
A full 7 years later and very little has in fact changed with Virtual Gibbs, the Sentinel protection remains intact and is largely unchanged, there is also the addition of FLEXNet to the mix, again using the most basic checkout, techniques from my FLEXlm page should enable easy recovery of the seeds. The only mildly interesting points to note are that the feature names are constructed using calls to sprintf with format specifiers such as "%s%c%c%s" & "%s%c%c%s%d", this provides trivial protection from feature name digging, the features will also only be checked out if they can be found in the license file, this prevents simplistic feature recovery by breakpointing lc_checkout().