This tutorial is part of a litlle project on a Hardlock dongle.
We know in these last years that dongles protections have become very hard to break, the old 'bpio' era is coming to the end!, and the dongles coders have learnt a lot. They have learnt an important lesson too, give the programmer that wants protect his software ways to do so without touching code. Encrypted code and heavy use of memory on dongles are normal these days. Some notes on the type of dongle that I've choosen :-
Hardlock (Black) from Aladdin http://www.hardlock.com/
I don't want explain in detail this type of dongle, check the website above. I give you only some notes. This black toy is probably the most diffused type of Hardlock dongles, the code name is E-Y-E if you don't know why check an image of this. It is equipped with memory and with the famous custom ASIC chip, in a few words it is possible to program an encryption algorithm directly in the hardware. This type is good for our purpose, because the information and the conclusions at which we'll arrive will be good for all models of Hardlock. The target that I've chosen is :-
Dips ver 4.051, http://www.rocscience.com, a snip at $1200
" DIPS is a program designed for the interactive analysis of orientation based geological data. The program is a tool kit capable of many different applications and is designed both for the novice or occasional user, and for the accomplished user of stereographic projection who wishes to utilise more advanced tools in the analysis of geological data ."
The main purpose is not the cracking of this program, it's an easy target!, but from this we want to study the entire set of HL_API for the Hardlock series. Before I start, I want to explain the ways a programmer can protect his software with a Hardlock dongle (don't forget to read the manual too, it is a mine of information). In general there are two ways, one easy and fast permits protecting a file without touching the source code, the second way uses a set of high level API (HL_API) that a coder can use in his program with a simple call of specific functions. The API is stored in one or more obj/lib files to link with the exe.
Dips is protected with this second way, the coders have implemented
the protection within their code, with the help of this set of
H(igh) L(evel) API functions. For us this target is very good
to start a study on the Hardlock dongle
.we can learn in
which way these functions are implemented in assembly language,
in which way they communicate with the dongle driver, and most
important we can learn to find the code used in the Aladdin protection.
First thing to do is retrieve all possible documentation about Hardlock API. There are two important docs from Aladdin :-
- HL_API (pdf), Apilow (doc).
HL_API is the user manual and Apilow is a good description of all these API's at low level (assembly). Both manuals are very clear. Study them both very well. Lets start with the first manual, chapter 3 gives a full explanation of each function. All functions are important but (for now) 5 of these are VERY important!, because they are very common in a Hardlock protection with API, lets see which they are :-
3. HL_READ / HL_READBL
4. HL_WRITE / HL_WRITEBL
The installation asks for a SerialNumber, here we can use any string of 17 chars .later we find out why. Now we can run the program and the classic message box 'Error: Hardlock not found'. Obviously set a bpx on MessageBoxA and restart the exe .we are here :-
:00465301 MOV EAX, D, [005E0418]
:00465306 CMP EAX, EBX
:00465308 JE 00465345 ß Jump from here, start without dongle.
:0046530A MOV ECX, 005E0048
:0046530F CALL 0040A570
:00465314 PUSH 00001000
:00465319 PUSH 005895C8
:0046531E PUSH 005E0222
* Reference To: USER32.MessageBoxA, Ord:01BEh
:0046532A CALL D, [0051E170] ß MessageBoxA.
:00465330 POP EDI ß We land here!.
:00465331 POP ESI
:00465332 POP EBP
:00465333 XOR EAX, EAX
:00465335 POP EBX
* Referenced by a (U)nconditional or (C)onditional Jump
:00465345 PUSH EBX ß Program starts.
:00465346 PUSH EBX
:00465347 PUSH 00000600
:0046534C CALL 004A8D10
:00465351 CALL 00461DD0
:00465356 CALL 004165F0
:0046535B CALL 004404C0
If we scroll up some lines we find the first good conditional jump to reverse in order to bypass the first check, easy and stupid, to be honest I hate the easy things and the stupid things. Scroll up until at the start of this routine :-
:00464F80 MOV ECX, 005DFF38
:00464F85 JMP 00464ED0
:00464F90 MOV EAX, D, FS:
:00464F96 PUSH FFFFFFFF
:00464F98 PUSH 005139B2
:00464F9D PUSH EAX
:00464F9E MOV D, FS:, ESP
:00464FA5 SUB ESP, 40
First we need to know if within this routine the code starts to scan the LPT ports, or if this routine is only the end to show the error message. With a quick look at this routine we can see some strange 'CALL', look, this program is built in Micro$oft Visual C++ (the MFC dlls are linked) and every function inside the program needs to take care of the stack at the start of the function and at the end of the function .It's normal code this :-
CMP EDI, ESI
xxxxxxxx PUSH EBP * Save caller's EBP frame. MOV EBP, ESP * Set up new EBP frame. SUB ESP, xxx * Number of bytes needed for local variables. ............ ADD ESP, xxx * Number of bytes added before for the local variables and now clean. POP EBP * Restore the original EBP. RET * The stack now is OK, retrieve the return address and return.
The caller pushes on the stack the parameters and waits for the call end. The called (function) retrieves these from the stack allocates space for local variables (if necessary) and when it has finished resets the stack and returns at the caller. But some call uses a different way .look :-
:0046502F LEA EAX, D, [ESP+18]
:00465033 LEA ECX, D, [ESP+18]
:00465037 PUSH EAX
:00465038 PUSH ECX
:00465039 PUSH EDX
:0046503A PUSH 00005C6A * Strange call ('C' declaration for this call/function).
:00465044 MOV ECX, D,[005E0444]
:0046504A ADD ESP, 10 * The caller has to clear the stack.
:0046504D AND EAX, 0000FFFF
:00465052 JNE 00465063
Here is the caller that has to clear the stack .these functions are not MFC functions (some MFC functions require that the caller clean the stack) .these calls are used to call functions in the lib/obj files these functions have a 'C' declaration and the stack is not cleared. Cleaning the stack is a caller's work!. OK, so each 'call' that has a clean instruction after can be a 'call' to a HL_API function. Lets see the first :-
:00465037 PUSH EAX * EAX = 0 (4th parameter).
:00465038 PUSH ECX * ECX = 0 (3rd parameter).
:00465039 PUSH EDX * EDX = 1 (2nd parameter).
:0046503A PUSH 00005C6A * 1st parameter.
:0046503F CALL 00425120 * Strange call .('C' declaration).
:00465044 MOV ECX, D,[005E0444]
:0046504A ADD ESP, 10 * The caller has to clear the stack.
:0046504D AND EAX, 0000FFFF * If EAX = 0 AND with FFFFh sets the ZeroFlag.
:00465052 JNE 00465063 * JMP if ZeroFlag is not set (EAX = 7 = NO_DONGLE).
4 parameters pushed on the stack and a return value of '7' in EAX. From the number of parameters and from the return value we can suppose that this is the HL_LOGIN function. We have :-
MOD = 5C6A address of the module.
ACCESS = 1 local dongle.
REFKEY = 0 not used.
VERKEY = 0 not used.
We change the STATUS_VALUE in EAX from 7 to 0 (STATUS_OK) and we step forward. Soon we arrive at the second 'strange function' look again the instruction after the CALL. OK we are on the right way :-
:004650D2 PUSH ESI * 2nd parameter.
:004650D3 PUSH ECX * 1st parameter.
:004650D4 CALL 004250C0
:004650D9 ADD ESP, 8 * The caller has to clear the stack.
:004650DC TEST AX, AX * AX = 1.
:004650DF JNE 0046514F
Now only 2 parameters are pushed on the stack .and the return STATUS_VALUE is 1 (NO_INIT), check the parameters to see what this call tries to do with the dongle:
seems a costant = 1
PUSH clear is a memory address .and guess what there's . '33444555 ' YES 8 bytes of OUR SERIAL #'.
This 'call' is nothing else than a call to HL_CODE. The return STATUS_VALUE is 1 and not 7 don't ask me why ask Aladdin coders. Its easy to understand that the program decrypts the serial number with the alghorithm in the dongle and after it performs a check. We change the STATUS_VALUE in EAX from 1 to 0 and we can go on. Well this simple logic in using the dongle is repeated 6 times (under different options, open, save,drawgraphic etc.) not difficult reversing. OK, now it's time to look at the second manual, Apilow.
" ...You should follow the steps listed below to guarantee the successful implementation of Hardlock:
Allocate memory for the API descriptor and fill the structure with binary zeros (0).
Prepare the structure for initialization by setting the module-dependent parameters as well as the corresponding function number. You will need to determine whether local and/or remote access can be made to the Hardlock. It would make sense to allow both options to be able to respond to HL-Server automatically when working with an application in network .."
The manual says better allocate memory for the API descriptor. Do you remember the famous API structure seen above in the HL_API manual. The important thing is this structure is the core of the LL_API and obvious HL_API. All parameters and FUNCTION NUMBER are passed through this structure. Lets look on.
"The API descriptor contains all necessary information to query Hardlock E-Y-E from your software. Some fields of the structure are used for either passing arguments or receiving information from the API. The descriptor consists of 256 bytes and is described on the following page".
I'll show only the most important for our reversing purposes :-
holds the type of dongle used.
0 - Hardlock E-Y-E
1 - Reserved
2 - Reserved
Only Hardlock E-Y-E is supported .I'm not sure about this. This field is set at the initialization of the API.
HARDWARE 10 bytes
is filled with 3 different types of
1 DW Address of the dongle
2 DW Register number in the dongle to perform an access
3 DW Value read or write to the register
4 DW Not used
5 DW Not used
Very important to monitor all that happens in this type of operation on the dongle!.
OK, enough. With this information we can go in deep and for example step in (with SoftICE) in the 'call' that we have seen in the code of our target to look inside at the 'lib' functions. Well lets look at HL_LOGIN :-
:00425120 MOV EAX, D,[005DFD38]
:00425125 SUB ESP, 0C
:00425128 PUSH ESI
:00425129 XOR ESI, ESI
:0042512B CMP EAX, ESI
:0042512D JNE 00425139
:0042512F MOV EAX, 005DFC38
:00425134 MOV D,[005DFD38], EAX
:00425139 MOV CX, W,[EAX+1C]
:0042513D MOV EDX, D,[EAX+34]
:00425140 MOV, W,[ESP+04], CX
:00425145 MOV CX, W,[EAX+1E]
:00425149 MOV D,[ESP+08], EDX
:0042514D MOV W,[EAX+06], SI
:00425151 MOV EDX, D,[005DFD38]
:00425157 MOV AX, W,[ESP+18] <-- 2nd parameter of HL_API.
:0042515C MOV W,[ESP+0C], CX
:00425161 MOV W,[EDX+1C], AX
:00425165 MOV ECX, D,[005DFD38]
:0042516B MOV DX, W,[ESP+14] <-- 1st parameter of HL_API.
:00425170 MOV W,[ECX+08], DX
:00425174 MOV EAX, D,[005DFD38]
:00425179 CMP D,[EAX+34], ESI
:0042517C JNE 00425197
:0042517E MOV [EAX+18], 0020
:00425184 MOV EAX, D,[005DFD38]
:00425189 PUSH EAX
:0042518A CALL 00425070
:0042518F MOV EAX, D,[005DFD38]
:00425194 ADD ESP, 4
In the above simple graph we can see that HL_API is the layer above LL_API. The LL_API is the layer that performs operation on the dongle through the Hardlock virtual driver. The structure is like a channel used to pass and retrieve information from the highest layer (HL_API) to the lower layer (vxd). We know that the main code to use the dongle is in Hardlock.vxd (ring 0 code) and we know too that it's a dynamic virtual driver, loaded with the CreateFileA, and used with DeviceIoControl.
At 00425189 if we look at the registers in SoftICE, we have :-
EAX = 005DFC38
EBX = 005DFC38
ECX = 005DFC38
EDX = 005DFC38
ESI = 00000000
EDI = 00000000
Anyway this is the dump of our structure in memory just before the code calls HL_LOGIN in the vxd :-
We can see the port field is 0000 (scan for all LPT ports) the remote field is 0001 (only local dongle) the function number is 0000 (HL_LOGIN) and the status code is 0007 this doesn't mean that there's been another test before! but only that the coder before to call the function has prefered setting this field at NO_DONGLE value. Well, now go in deep, one way (probably not the best) is put a bpm on the function number field above .in this way we know exactly when the vxd retrieves this field and decides which part of its code it has to execute.
SoftICE breaks many times in the code of our program, go on until SoftICE breaks in the middle of the hardlock.vxd :-
MOV CX,[EAX+18] ß Here the vxd mov the function
number in CX.
TEST ECX, ECX ß Is function number = 0?.
MOV EAX, [EBP+08] ß Yes function number 0.
XOR ECX, ECX
MOV CL,[EAX+52] ß Test a byte in the reserved fields?.
TEST CL, 8
MOV EAX, [EBP+8]
MOV AX, [EAX+4C] ß Save a word from the reserved fields?.
MOV [EBP-8], AX
MOV EAX, [EBP+8]
MOV AX, [EAX+1E] ß Quite interesting, save the port field.
MOV [EBP-10], AX
MOV EAX, [EBP+8]
MOV WORD PTR [EAX+4C], 0100 ß Replace the saved word with 0100.
MOV EAX, [EBP+8]
MOV WORD PTR [EAX+1E], 0000 ß Replace with 0000 (scan for all ports).
MOV EAX, [EBP+8]
ADD ESP, 4
MOV [EBP-4], AX
The code test if the function number is 0 if so it retrieves the value in the port field (0) then it prepares some internal data to start the real scan on the LPT ports. Without giving a you a lot of 'heavy code' I explain what happens inside the call. Here a main loop scans the LPT ports on the addresses 278h 378h and 3BCh each address is checked in a secondary loop .and everytime the status field in the structure is updated. OK, now we can return out from HL_LOGIN in our main program. With the same technique we study the HL_CODE function :-
:004650D2 PUSH ESI * 2nd parameter=1 (1*8 number of byte
blocks to decrypt).
:004650D3 PUSH ECX * 1st parameter=address of 1*8 blocks to decrypt.
:004650D4 CALL 004250C0 * HL_CODE, we step in!.
:004650D9 ADD ESP, 8
:004650DC TEST AX, AX * AX = 1 = NO_INIT.
:004650DF JNE 0046514F
Well, lets step in the call and see what happens :-
:004250C0 MOV EAX, D,[005DFD38] * Address of the structure
:004250C5 TEST EAX, EAX * is 0?.
:004250C7 JNE 004250D3 * No! jump.
:004250C9 MOV EAX, 005DFC38
:004250CE MOV D, [005DFD38], EAX
:004250D3 MOV ECX, D,[ESP+04] * 1st parameter = addr byte to decrypt in ECX.
:004250D7 MOV D, [EAX+12], ECX * Store in the structure.
:004250DA MOV EDX, D,[005DFD38] * Address of the structure in EDX.
:004250E0 MOV AX, W,[ESP+08] * 2nd parameter = num blocks to decrypt in AX.
:004250E5 MOV W,[EDX+16], AX * Store in the structure.
:004250E9 MOV ECX, D,[005DFD38] * Address of the structure in ECX.
:004250EF MOV [ECX+18], 000B * Store in the structure the number of function B (HL_CODE).
:004250F5 MOV EDX, D,[005DFD38] * Address of the structure in EDX.
:004250FB PUSH EDX * Pass this as parameter at the next call in the lib.
:004250FC CALL 00425070 * HL_CODE.
:00425101 MOV EAX, D,[005DFD38] * Address of the structure in EAX.
:00425106 ADD ESP, 4 * Clear the stack.
:00425109 MOV [EAX+16], 0000 * Set a 0000, num of blocks to decrypt.
:0042510F MOV ECX, D,[005DFD38] * Address of the structure in ECX.
:00425115 MOV AX, W,[ECX+1A] * Retrieve from the structure the STATUS_VALUE.
OK, now we know that we are in the right place we use the same bpm on function number in the structure to see when the hardlock.vxd uses it. Just before the call 00425070 we set the bpm, Ctrl+D and after the usual checks within the program we land in hardlock.vxd, what MessageBoxA 'Hardlock error memory not found! ' and the vxd code, we never see the vxd code more, what happened. The first HL_LOGIN function is important because it sets internal data in the 'lib' code according to the result of the function (this is the main reason when the manual says 'every call to functions must be performed after the HL_LOGIN and before HL_LOGOUT functions). If the vxd returns a code 7 (NO_DONGLE) at the HL_LOGIN the 'lib' code sets some data, this way other calls to the dongle never reaches the vxd's code.
To understand better, we repeat the last part for the HL_CODE function, just before the call 00425070 we set again the bpm 005DFC50 and see what happens when the program code retrieves this field (function number), at the first break we have :-
:004791EE CMP W, [EBX+18], 000B * SoftICE breaks here.
:004791F3 JNZ 00479205 * No jump away.
:004791F5 CMP W, [EBX+16], 0000 * Yes the function is B, check if the number of blocks is 0.
:004791FA JNZ 00479207 * No number of blocks to decrypt are valid, jump.
:004791FC MOV AX, 0000
:00479200 MOV [EBX+1A], AX * Move AX = 1 (NO_INIT) in the STATUS field.
:00479204 RET * Exit.
:00479205 JMP 0047924D
:00479207 CMP B, [EBX+000000FE], 00 * Check a byte in reserved fields, if it's 00 (No dongle).
:0047920E JNZ 00479216 * Not 0 means HL_LOGON is been correct (dongle present) jump.
:00479210 MOV AX, 1 * The byte is 0 (HL_LOGON failed).
:00479214 JMP 00479200
:00479216 CALL 00478FB4
The above code is very clear, on function B (HL_CODE) the code in the 'lib' checks one of the internal data bytes (offset FEh from the start of the structure) to see if the HL_LOGIN is really succeeded or not (0). If not (0) set directly the status field at 1 and return. OK, go on Ctrl+D and we see what happens, the code breaks here :-
:00425013 MOV W, [EAX+18], 0001
The 'lib' code is sure that something has gone wrong and sets the function number at 1 (HL_LOGOUT) to release the APIs :-
:00425034 MOV W, [EAX+18], 001F
Again the 'lib' code now uses function 1Fh to release every link with the dongle, bye bye vxd code!. Anyway, now we know something more about the HL_API and LL_API. We have an idea of what we can meet in a program protected with Hardlock and more importantly we know how we can move through the program code.