|
Protecting software with hardware keys is another, less-used alternative for software protection. In this protection scheme, a copy-protection device, called a dongle, is connected to an I/O port on the computer and must be present for the protected program to run.
There are two types of hardware-key protection:
• The program cannot be started without the hardware key.
• Some functions of the program are limited without the hardware key.
HASP and Sentinel are two of the most widely used hardware keys and are probably the best, so I will describe both of them.
Program Cannot Be Started Without the Hardware Key
Most hardware keys are very simple. The program sends data to the port where the hardware key is supposed to be, and waits for a reply. If the program doesn't receive a reply, it refuses to run and gives an error message.
When a hardware key isn't connected, the program cannot be started More advanced hardware keys encode the data sent to the port. Or the hardware key could include an EPROM, with important parts of the program built into the hardware key itself. In this case, if the cracker has only the program and not the hardware key, it is almost impossible to remove the protection.
There are several ways to locate the routine that tests for the presence of the hardware key. For one, an API hook that tests for the key's presence during each API call is often used. Another method that's commonly used is to call the API by means of a function specific to the particular key. Some more advanced hardware keys use their own drivers.
If a cracker learns to remove a certain type of hardware-key protection, it's often not a problem for him to remove such protection at any time in the future. Of course, companies are continually trying to improve their protection programs by writing better drivers, so removing hardware protection is not always a given. Still, it's easy enough to find software emulators for the most common hardware keys that try to emulate the key's presence.
It is very important when using a hardware key to manage the protection in the program code itself, and not to rely on drivers or API calls from the manufacturer. Crackers often know these drivers and API calls very well, and they don't have much of a problem overcoming them.
When a cracker can't find a suitable emulator for a particular key type, he will try to emulate its behavior directly in the program code. When doing so, he usually will not try to change the driver's code, because it's typically well protected against these attacks. Instead, he will insert the emulation code into the program. The best protection against this crack is to perform a multiple cyclic redundancy check (CRC) against program changes both in the file and in memory.
One major downside to using hardware keys is that because a dongle must be supplied with every copy of the program, the program's price and its cost to manufacture increase, and you may encounter distribution problems . Therefore , hardware keys are mostly used with very expensive programs and not with shareware.
A hardware key may be a good solution for smaller teams developing custom-made programs, but if you choose to go this route, buy the complete protection instead of trying to develop your own because it isn't easy or cheap to do.
Some Functions Are Limited Without the Hardware Key
The principle of this protection is very simple—when no hardware key is connected, some important functions of the program won't work. Once a hardware key is connected, the program is fully functional. When these important functions are contained in the hardware key itself, this type of protection is very good. The key may also contain codes for decoding these functions right in the memory.
It is almost impossible to remove this protection without the key, especially when the encoding is good. However, if these functions are only blocked, and are unblocked once the hardware key is used, the cracker will have a much easier time removing the protection, and can probably do so even without the hardware key.
HASP Hardware Keys
The HASP series of hardware keys , from Aladdin Knowledge Systems (http://www.ealaddin.com), offers several options. HASP installs its own drivers when the software is installed, and this software is used later for communicating with the hardware key. HASP has special drivers for DOS, Windows 9x/NT/2000/XP, and Mac OS X.
Program developers wishing to protect their product with HASP must communicate with the hardware key by means of the installed drivers, because everything is secured by HASP API calls. To see where these calls are performed in a HASP-protected program, look for cmp bh, 32:
HASPCall: ...
cmp bh, 32 ;test to see whether this
;is a higher HASP service
jb jump
mov esi, dword ptr [ebp+28] mov eax, dword ptr [esi]
jump: mov esi, dword ptr [ebp+20]
mov esi, dword ptr [esi] push ebp
call Hasp( ) ;the basic HASP service is called here
pop ebp
mov edi, dword ptr [ebp+1C]
mov dword ptr [edi], eax ;saves the return value
mov edi, dword ptr [ebp+20]
mov dword ptr [edi], ebx ;saves the return value
mov edi, dword ptr [ebp+24]
mov dword ptr [edi], ecx ;saves the return value
mov edi, dword ptr [ebp+28]
mov dword ptr [edi], edx ;saves the return value
The basic HASP service always consists of the same call. The program makes decisions according to the parameters by which it was called, and then it decides which HASP service to call.
If you look one level higher in the program code, you will see code like the following:
push eax
push ecx
000047FE push 000015C9 push ebx push edi push 00000003
mov [esp+38], 0000001A call HASPCall
;password1 ;password2 ;lptport
;HASP service number 3
;address
;calls the ReadWord ( ) service
In this case, HASP function no. 3 was called: ReadWord(). All other services will be called in this way. Let's look at the most important and frequently used functions.
Function no. 1: IsHasp()
This function is always called first. It considers whether the hardware key is attached. Simply changing the return value of this service will absolutely not overcome HASP.
Input values:
• BH = 01
• BL = LPT port
Return values:
• EAX = 0 — Hardware key wasn't found
or
• EAX = 1 — Hardware key was found
Function no. 2: HaspCode ()
This function is usually called right after the IsHasp() function. password1 and password2 are codes that are used for communication with the hardware key. The seed code determines the return values.
Input values:
• BH = 02
• BL = LPT port
• EAX = seed code
• ECX = password1
• EDX = password2
Return values:
• EAX = code1
• EBX = code2
• ECX = code3
• EDX = code4
Developers using HASP often make a mistake when testing the return values. Basically, it is enough to test two values or to perform the test using a more complex mathematical calculation. It is also possible to test more values for each return code, which makes the cracker's life more difficult.
Function no. 3: ReadWord()
This function reads dword (a word) from the HASP memory. The address from which the reading will be performed is located in EDI. You must multiply the address by two to find it.
Input values:
• BH = 03
• BL = LPT port
• ECX = password1
• EDX = password2
• EDI = address
Return values:
• EBX = Read data
• ECX = status 0 — Correct, otherwise there was an error (you can find the error description in HASP documentation)
Function no. 4: WriteWord()
This function writes dword (a word ) into the HASP memory. The address where the writing will be performed is located in EDI. To find out the address, you must multiply it by two, since it is a word.
Input values:
• BH = 04
• BL = LPT port
• ECX = password1
• EDX = password2
• EDI = address
Return values:
ECX = status 0 — Correct, otherwise there was an error (you can find the error description in HASP documentation)
Function no. 5: HaspStatus()
Use this function to acquire the following information about HASP: memory size, HASP type, and LPT port.
Input values:
• BH = 05
• BL = LPT port
• ECX = password1
• EDX = password2
Return values:
• EAX = memory size
• EBX = HASP type
• ECX = LPT port
Function no. 6: HaspID()
Use this function to learn the HASP ID. EAX contains the lower part of the ID, and EBX contains the higher part.
Input values:
• BH = 06
• BL = LPT port
• ECX = password1
• EDX = password2
Return values:
• EAX = ID lower
• EBX = ID higher
• ECX = status 0 — Correct, otherwise there was an error (you can find the error description in HASP documentation)
Function no. 50: ReadBlock()
This function reads a memory block from HASP. The address from which the reading will be performed is located in EDI. The block length to be read is located in ESI, and the address where the read data will be saved is located in ES:EAX.
To learn the actual address from which the reading will be performed, multiply the address in EDI by two, since the data is read by words.
Input values:
• BH = 50 or 32h
• BL = LPT port
• ECX = password1
• EDX = password2
• EDI = start address
• ESI = data block length
• ES = buffer segment
• EAX = buffer offset
Return values:
• ECX = status 0 — Correct, otherwise there was an error (you can find the error description in HASP documentation)
Function no. 51: WriteBlock()
This function writes a memory block into HASP. The address from which the reading will be performed is located in EDI. The block length to be written is located in ESI, and the address from which the data to be written will be read is located in ES:EAX.
To learn the actual address into which the writing will be performed, you must multiply the address in EDI by two, since the data is written by words.
Input values:
• BH = 51 or 33
• BL = LPT port
• ECX = password1
• EDX = password2
• EDI = start address
• ESI = data block length
• ES = buffer segment
• EAX = buffer offset
Return values:
• ECX = status 0 — Correct, otherwise there was an error (you can find the error description in HASP documentation)
Naturally, HASP uses other functions besides the ones just discussed, including functions such as: SetTime(), GetTime(), SetDate(), GetDate(), Writebyte(), and Readbyte(). I will not describe these here, however , since they are easy to understand if you have the HASP documentation . The HASP documentation is really wonderful, and it is absolutely essential for all developers.
HASP needs only about 128 bytes of memory, and a skilled programmer can use even this small amount well. For example, he might use the read data to decode a certain part of the program.
To avoid testing to see whether each byte was read correctly, it is possible to use a CRC test for the read data. This will confirm that the data was read correctly without revealing to a cracker the data that was to be read.
Another good way to use HASP is to write important data into the hardware key to be read and used later. This works because, when dealing with HASP, crackers have to write an emulator that emulates HASP drivers. Probably the most difficult part is writing into HASP memory, because the emulator mostly runs in a code area where writing isn't allowed. Crackers, therefore, ignore this function and don't emulate writing at all; they only return a 0 value in ECX to show that the service was successful.
Finally I'm coming to the HASP Envelope treat. HASP Envelope is a PE file encoder that encodes the original EXE file. When started, it will decode the original EXE in real time and run it. I don't want to deprecate its functionality at all, but removing the HASP Envelope is no problem unless correct decoding depends upon the connected hardware key. In this case, the return values aren't tested but are decoded directly, which is a cracker's nightmare. Of course, this protection can also be removed with a correct key, though in most cases the HASP Envelope will not be removed because of possible related problems (CRC test).
HASP drivers, themselves, are well programmed, and it is difficult to debug them. They contain many anti-debugging tricks and other finesses against curious individuals. Still, even with all these measures in place, several crackers have managed to get through them to write an almost 100 percent functional emulator. Of course, this is the problem we continue to encounter with all commercial software protection. Naturally, a new version of HASP drivers appeared soon after that emulator was released, but you can be sure that it won't take long before a new emulator appears, too.
HASP is definitely an excellent protection program when correctly used. However, if program developers simply rely on HASP, and after calling a function only test the correctness of the return values, it won't really be difficult for a cracker to remove the protection from the program. HASP's biggest disadvantage comes from the added costs connected with selling a HASP-protected program. Therefore, HASP protection is really only worth considering for expensive programs that will not be distributed via the Internet.
Sentinel Hardware Keys
Sentinel, from Rainbow Technologies (http://www.rainbow.com), is the other popular hardware key option. Because Sentinel is very similar to HASP, I will not describe it in detail. Sentinel documentation is as good and detailed as HASP documentation, making it easy to use.
There is, however, one particular aspect of Sentinel worth mentioning, and that is the fact that there is a test before each API call for the hardware key to see whether the packet communication was initialized correctly. It looks like this:
cmp word ptr [esi], 7242 is correct
mov ax, 0002 ;sets the error number
pop edi pop esi ret 000C
Value 7242 is a packet marker. If it is not found at the beginning, error 2, Invalid Packet, will be set.
You can easily find each API call for the hardware key service simply by looking for this test. The protection essentials are the same as for HASP, and it is again necessary to prevent emulation in the program memory.
|