-
-
Software Emulation of HASP keys
-
发表于: 2005-1-9 23:41 8224
-
转一篇HASP的文章
Software Emulation of HASP keys
By exefoliator, 2000.
Abstract.
The article which follows explains the operation of the HASP family of software protection keys (dongles) with particular emphasis on the HASPCODE service. Aladdin Knowledge Systems (AKS), the originators and suppliers of this range of products, have made the claim that their keys cannot be emulated in software. This article not only shows this to be a wrong assertion, but also sets out just how to go about collecting information relevant to any HASP key from the executable it is supposed to protect. The reader should note that the information given may not apply to HASP4, the latest generation of keys from AKS. The author will endeavour to establish the nature and operation of the HASP4 keys and report on the findings as they become available.
1. Introduction.
Aladdin Knowledge Systems (henceforth referred to as "AKS") supplies a range of HASP (Hardware against software piracy) keys, commonly known as "dongles," for protecting software from unlicensed use and/or distribution. AKS claims that using HASPs makes programs uncrackable, mainly because of their ASIC (application-specific integrated circuit) chip design. This aspect is purported to be sophisticated to the extent that the hardware cannot be emulated by a general-purpose digital computer, i.e. a Turing Machine.
By my nature, I am mistrustful of all forms of authority, particularly those who profess to have my best interests at heart, while keeping me in the dark about just what knowledge they are privy to that I am not. Telling me that something is not possible without supplying a good and sufficient reason why it should be so is the surest way of getting me to find out for myself. Moreover, despite the current neo-fascist efforts to make reverse engineering illegal (which, if successful, will effectively kill one of the cornerstones of the free market system - by protecting product stasis), it is my belief that it is (a) morally wrong to prohibit it, (b) impractical to enforce such a concept and (c) restrictive to competition and, ultimately, to economic growth. Go figure.
So much for the motivation for writing this article. Now some history. I had one brief encounter with HASP about five years ago when I was tasked with protecting a DOS program with a dongle. I read the AKS literature and decided to use the LPM (linkable protection module) which is a piece of object code one can link into one's program. The LPM provides an interface to all of the HASP calls (more on which later) so that the programmer can decide where and when and how to access the dongle. What worried me was that the LPM for DOS was 27kB in size, and the program I was to protect needed every byte of memory it could scrape together.
So I decided to find out why the LPM is such a hog compared to what it does. Turbo Debug and a day later the answer was plain to see: the LPM does a few useful things from a security perspective - timing traps, hooking debug interrupts, decrypting the memory code image before the call executes and re-encrypting it afterward - but mostly it is full of redundant rubbish which does nothing at all except waste system resources. I rewrote my own LPM in assembler which yielded an object image of just less than 2kB, with the same security measures as in the original LPM.
Recently I again had occasion to evaluate HASP as a protection mechanism. I found that the only thing which had changed in the HASP line during the last five years was AKS's blurb on how uncrackable their product was (see http://www.aks.com/hasp). The ASIC was just the same, the LPM was just the same, except for a 32 bit wrapper and some extra bloat, and the HASP services were just the same. On the strength of this lack of product development, I decided against using HASP.
2. The HASP Product Line and HASP services.
The HASP product line consists of several keys. The most basic is HASP-3, followed by MemoHASP-1, MemoHASP-4, TimeHASP and NetHASP. Each of these more sophisticated keys provides some read/write memory (between 112 and 512 bytes) and a unique 32 bit HASP identification code. The memory can be read/written in individual 16 bit words or in contiguous blocks. In addition, TimeHASP incorporates an on-board real time clock that can be set or queried. NetHASP can be used over networks to control the number of simultaneous users of a program.
HASP-3 supports only three basic services, i.e. ISHASP (reports whether a HASP key is connected to the computer or not), HASPSTATUS (reports the HASP key type) and HASPCODE (returns a 64 bit code for a given 16 bit seed code). The other keys also support these services but include extended services for reading/writing HASP memory, reading the HASP ID code and accessing the on-board clock, as applicable. The assembler calling conventions for each of these extended services can be found in the HASP documentation included with the developer's kit.
At base, the three services listed above use the 16 bit calling conventions given below, even in a Win32 environment:
Service: Call: Return:
ISHASP BH <- 1, BL <- LPT no. AX = 0 (no HASP) or 1 (HASP found)
HASPCODE BH <- 2, BL <- LPT no. AX = return code 1
AX <- seed code BX = return code 2
CX <- password1 CX = return code 3
DX <- password2 DX = return code 4
HASPSTATUS BH <- 5, BL <- LPT no. AX = memory size
CX <- password1 BX = HASP type
DX <- password2 CX = actual LPT no.
With the exception of HASPCODE, the principles of operation of the above services are easy to understand and hence emulate in a fairly obvious and straightforward way. This is also true of the extended services. It is therefore no surprise that the HASPCODE service is the foundation upon which almost all of HASP's security is based, and it is the operation of just this service that will be discussed in detail here.
3. HASP Security Measures.
Before dealing with the low-level details of HASPCODE, a little needs to be said about some of the measures taken by AKS to enhance software security and protection. The three most important of these are virus/tampering detection, the Pattern Code Security (PCS) facility and the HASP envelope.
Virus/tampering detection is a relatively standard cyclical redundancy check (CRC) run on (a portion of) the executable's disc file image. The CRC signature(s) must match a set of hard-coded ones. The routine that performs this check can be found and disabled in fairly short order by comparing program flow between successful and failed CRCs.
Pattern Code Security is a facility which updates up to 25 instances of a predefined HASP data structure with a single call to the HASP routine. These structures are static and can live anywhere in the executable's memory space (provided the memory is writable). The exact layout of a PCS structure is defined in the HASP developer kit, but it includes an ANSI string signature ('$HASP$PCS'), the service, the service parameters and space for the return values. All of the PCS structures can be found by finding the PCS masterlist. This is a structure with the ANSI string signature '@SCP@PSAH', followed by the number of PCS entries (word) and a list of 32 bit offsets to these structures. Note that this masterlist will not be found in the disc image of the executable if the HASP envelope was used to protect the program since it is encrypted. Once again, given the address of the PCS masterlist, PCS emulation is not especially difficult.
Finally, and probably most importantly, the HASP envelope provides software security that is "virtually impossible to crack," according to AKS's blurb. The basic idea is to encrypt most of the executable using the HASPCODE service to obtain an encryption key and to decrypt the program once it is loaded into memory, provided the proper HASP key is attached to the computer. The idea itself seems to make sense and appears to be logically sound until one subjects it to closer scrutiny. As will be described later, the HASP envelope divulges two vital pieces of information about the HASP key, one of which will be instrumental in emulating the key in software. The only really important aspects here are that (a) the HASP routine usually occurs twice in the executable - once for the envelope and again for the executable itself, and (b) the breakpoints for intercepting HASP calls can only be set after the decryption routine(s) are complete.
It should be noted that the envelope can be applied to an executable more than once. In this case a separate HASP routine exists for each envelope, but this does not make the program significantly more secure because the en/decryption scheme is the same for each envelope with only the crypto keys being different. As we shall see, getting these keys is a cinch once the outermost envelope is penetrated.
All of the cryptography routines used by AKS in the envelope that I have seen are 64 bit block ciphers - i.e. a 64 bit mask is generated and the target bytes are XORed to en/decrypt them. Nothing new there. The tricky bit is finding the first 64 bit block, which is got from the HASP key via the HASPCODE service. The envelope, like all Win32 applications, is bound by the conventions of good behaviour, and that presents perhaps the biggest chink in AKS's armour - the HASP envelope mustn't raise any exceptions or faults.
The decryption routine usually proceeds as follows:
1. A preliminary decryption routine, which does not depend on any HASP key data, is run on a block of memory to decrypt the envelope's HASP and main decryption routines.
2. The decrypted HASP routine is called with the ISHASP service to check whether any HASP key is attached to the computer.
3. If a HASP key is attached, the routine is then called with the HASPCODE service and a fixed seed code.
4. The HASPCODE return values are then compared directly with a set of expected values hard-coded into the envelope. No attempt is made to hide, encrypt or spread this comparison.
5. If the return values agree with the expected ones, the envelope calls the HASP routine again, requesting the HASPCODE service with a different seed code. The return values are used to prepare a 64 bit decryption key. The envelope then performs the main decryption and thereafter passes control to the executable.
It is at steps (3) and (4) that a great deal of valuable information is gleaned. By checking that the correct key is attached, the envelope divulges the two 16 bit passwords for the dongle and, more importantly, a complete set of return codes for a specific seed code. Later I will show how the latter fact reduces HASP's cipher strength from 64 bits to between 20 and 25 bits, putting it well within the limits of a brute-force attack. To be completely accurate, the cipher strength is actually only 16 bits because the seed code is only 16 bits - i.e. there are at most 65536 (= 2^16) unique 64 bit return code sequences. Thus, the envelope "protection," as implemented by AKS, does quite the opposite of what it was intended to do.
Before analysing the cryptography aspect further we need to know how the HASPCODE routine works.
4. The HASPCODE Algorithm.
The algorithm by which the HASP key generates return codes is surprisingly simple - a linear-congruential pseudo-random number generator is used to obtain a 6 bit (0 to 63) offset into a static 64 bit lookup table, and the return code is generated one bit at a time. The following assembly listing shows the complete ISHASP and HASPCODE emulation routine for a 32 bit environment.
80 FF 01 cmp bh,1 ; ISHASP requested?
75 05 jne @CheckTwo ; jump if not
31 C0 xor eax,eax ; else...
40 inc eax ; ...return eax = 1...
EB 67 jmp @EndHProc ; ...and exit
@CheckTwo:
80 FF 02 cmp bh,2 ; HASPCODE requested?
74 0A je @MakeCode ; jump if so
EB 60 jmp @EndHProc ; else do nothing
@HASPData:
db x0 x1 x2 x3 x4 x5 x6 x7 ; HASP static data
@MakeCode:
31 C9 xor ecx,ecx ; ecx <- 0 bit offs
31 DB xor ebx,ebx ; ebx <- 0 byte offs
E8 00 00 00 00 call @NextInst ; push next addr...
@NextInst:
5E pop esi ; ...and save to esi
@NextHBit:
53 push ebx ; save byte counter
66 BB 89 19 mov bx,1989h
F7 E3 mul eax,ebx
83 C0 05 add eax,5 ; seed <- seed*1989h+5
0F B7 C0 movzx eax,ax ; seed <- seed and FFFFh
50 push eax ; save seed
C1 E8 09 shr eax,9
80 E0 3F and al,3Fh ; al <- bit offset
89 C3 mov ebx,eax
C1 EB 03 shr ebx,3 ; ebx <- byte no.
80 E0 07 and al,7 ; al <- bit no.
51 push ecx ; save current code
88 C1 mov cl,al
B0 01 mov al,1
D2 E0 shl al,cl ; al <- bit mask
84 44 33 EF test byte ptr [ebx+esi-11h],al ; is bit set?
B0 00 mov al,0 ; assume not so
74 01 je @BitClear ; jump if clear
40 inc eax ; else set bit
@BitClear:
59 pop ecx ; ecx <- code string
D0 E5 shl ch,1 ; move to next bit
08 C5 or ch,al ; add latest bit
58 pop eax ; eax <- seed
5B pop ebx ; ebx <- byte count
41 inc ecx ; inc bit count
80 F9 08 cmp cl,8 ; 8 bits done?
75 C7 jne @NextHBit ; jump if not
F6 C3 01 test bl,1 ; byte count odd?
74 03 je @EvenByte ; jump if so
5A pop edx ; get ret code
88 F1 mov cl,dh ; add upper 8 bits
@EvenByte:
51 push ecx ; save ret code
31 C9 xor ecx,ecx ; clear code string
43 inc ebx ; inc byte count
80 FB 08 cmp bl,8 ; 8 bytes done?
75 B6 jne @NextHBit ; jump if not
5A pop edx ; edx <- ret code 4
59 pop ecx ; ecx <- ret code 3
5B pop ebx ; ebx <- ret code 2
58 pop eax ; eax <- ret code 1
@EndHProc:
C3 ret ; return to caller
The above code is tailored to be space efficient and to work regardless of where in memory it is loaded so that there is no need for any further address checking. It follows the machine code calling protocol for the ISHASP and HASPCODE services, as outlined earlier. Also, the code bytes are shown to reflect the actual size (114 bytes) and the signature of the emulation routine on an 80386 or later.
The only difficulty in applying the above to a given HASP key lies in finding the 64 bits of static lookup data (the 8 bytes appearing as x0 to x7 in the above listing). This is where the information previously collected from the envelope is very useful, and leads us into the next section.
5. Reconstructing HASP's Static Table.
The basic idea behind reconstructing the 64 bits of static HASP data is to recreate the sequence of pseudo-random bit offsets for the known seed code and extracting the corresponding bits in the known return codes, so building, at least in part, the lookup table for the dongle.
The procedure is as follows - given Seed and RetCode_1 to RetCode_4:
(1) write the four 16 bit return codes in order as a sequential bit string of 64 0s and 1s, not forgetting about byte reversals on 8086 processors, i.e. as LSB(RetCode_1) MSB(RetCode_1) LSB(RetCode_2) ... LSB(RetCode_4) MSB(RetCode_4)
(2) Construct a 64 character string of '?' - this will become the HASP static table bit string
(3) Let CurrentBitNo <- 0
(4) Let Seed <- (Seed*0x1989h+0x5h) and 0xFFFFh and BitOffs <- (Seed shr 0x9h) and 0x3Fh
(5) Let BitOffs <- 7+((BitOffs shl 1) and 0x70h)-BitOffs
(6) Get bit no. CurrentBitNo (from left to right, zero-based) from the bit string of the return codes in (1)
(7) Set the character in the string under (2) at position BitOffs (left to right, zero-based) to '0' or '1', in accordance with the state of the bit referred to in (6)
(8) CurrentBitNo <- CurrentBitNo+1
(9) if (CurrentBitNo < 64) then go to step (4) else stop
Once finished, the above routine yields a string with 0s, 1s and ?s. The ?s are those bits which were not referenced by the seed code, while the other bits are as they appear in the HASP key's static table in the correct order, viz. x0 x1 ... x7, when subdivided into groups of eight bits.
Generally, a single set of HASPCODE data composed of a seed code plus four return codes will reveal between 39 and 44 bits, leaving 20 to 25 unknown. A different seed code for which the return codes are unknown will reference between 14 and 19 of the unknown bits. At worst, these bits can be found by a brute-force search. However, reasonable guesses can be made as to the state of these bits because the static data exhibits some patterns and cycles that only become evident when it is represented as an eight-by-eight bit matrix. As an example, the bit matrix for the Memo/Time/NetHASP DEMOMA demo key has the following bit matrix:
x0 = 0 0 1 1 1 0 0 1 = 39h
x1 = 0 1 1 0 0 0 0 1 = 61h
x2 = 0 0 1 1 1 0 1 1 = 3Bh
x3 = 0 1 1 0 0 0 1 1 = 63h
x4 = 1 0 0 1 1 1 0 1 = 9Dh
x5 = 1 1 0 0 0 1 0 1 = C5h
x6 = 1 0 0 1 1 1 1 1 = 9Fh
x7 = 1 1 0 0 0 1 1 1 = C7h
Now suppose that we do not know this HASP key's static table, but that we have learned that for the seed code 11073 (decimal) this HASP key returns the code set
RetCode_1 = 14990 = 3A8Eh
RetCode_2 = 23754 = 5CCAh
RetCode_3 = 43929 = AB99h
RetCode_4 = 45507 = B1C3h
Upon applying the procedure given above, we obtain
[ 8E ] [ 3A ] [ CA ] [ 5C ] [ 99 ] [ AB ] [ C3 ] [ B1 ]
BitString(1) = "10001110 00111010 11001010 01011100 10011001 10101011 11000011 10110001
"OutString(9) = "00111001 01?000?? ?0?????1 0?1?0??1 1??1110? 1?0?0?01 100?11?? 1?000?11"
whence
x0 = 0 0 1 1 1 0 0 1 = 39h
x1 = 0 1 ? 0 0 0 ? ? = 40h, 41h, 42h, 43h, 60h, 61h, 62h or 63h
x2 = ? 0 ? ? ? ? ? 1 = (one of 64 possibilities)
x3 = 0 ? 1 ? 0 ? ? 1 = (one of 16 possibilities)
x4 = 1 ? ? 1 1 1 0 ? = 9Ch, 9Dh, BCh, BDh, DCh, DDh, FCh or FDh
x5 = 1 ? 0 ? 0 ? 0 1 = 81h, 85h, 91h, 95h, C1h, C5h, D1h or D5h
x6 = 1 0 0 ? 1 1 ? ? = 8Ch, 8Dh, 8Eh, 8Fh, 9Ch, 9Dh, 9Eh or 9Fh
x7 = 1 ? 0 0 0 ? 1 1 = 83h, 87h, C3h or C7h
In this case 24 bits remain indeterminate, but inferring bit values based on assumed patterns reduces this number. For example, the first column has one unknown bit in the third row and the bits appear to be composed of two groups of four. It is therefore reasonable to suppose that the unknown bit is a 0. Similarly, the unknown bit in column 5, row 3 is probably a 1, and the three unknown bits in column 8 are all likely to be 1s. Continuing in this way, other patterns can be sought and built upon to yield a few of the most likely bit matrices, each of which can then be tested either against the HASP envelope or against other HASPCODE requests until the correct candidate is found.
Having picked out the bit patterns for seven different HASP keys (the bit patterns and serial/batch numbers of which I will not divulge here for obvious reasons), it is safe, and indeed profitable, to infer the unknown bits of other keys, using the above example as a rough guide. One should look mainly for horizontal and vertical cycles and patterns, but diagonals can also prove useful, especially if the entire bit matrix is copied immediately to the right as well as below itself. Similar groups of two and four bits recur in all sorts of fairly self-evident ways.
With some trial-and-error, the remaining bits can normally be found in fewer than about 30 attempts.
Does AKS's claim of unsurpassed security still hold water in light of the above? The answer is, I think, quite obvious.
6. Completing the HASP Emulation.
With the knowledge that it is possible to construct a short piece of code that performs exactly the same function as the ISHASP and HASPCODE calls to the HASP routine, emulating the remaining services is almost trivial. Routines that do what the READMEMO, WRITEMEMO, HASPSTATUS, HASPID, READBLOCK and WRITEBLOCK services do can be coded in a straightforward way, making use of the main program's code space to read/write HASP "memory," if necessary. Of course, this is only required if the main program actually makes any of these calls to the HASP routine. Some difficulty may be experienced in the case where a TimeHASP is used and its on-board clock is accessed, but even this can be overcome by using the GetSystemTime() Win32 API function, if required.
Once a complete list exists of the HASP services a given program makes use of, a complete HASP emulation routine can be cobbled together without too much difficulty from the various bits and pieces. If the program makes use of PCS (see Section 3.), the emulation routine can be called from a loop which loads, executes and saves the PCS specifications sequentially from the data in the PCS masterlist and the PCS structures this list identifies.
By reversing the relevant decryption routines and adjusting any CRC or checksum routines, an executable can be patched so as to not require an actual HASP key. This can generally be achieved with a total patch size not exceeding 1kB. This may sound like a lot, but pales in comparison to the 32 bit HASP routine which is around 70kB in size - another victim of code bloat?
In some versions the block cipher mask depends on the en/decrypted bytes of the preceding en/decrypted block. This is a further safeguard against patching but is not particularly effective since the blocks immediately before and/or after the patch can be padded with adjusting bytes to ensure that other code and data are decrypted correctly.
7. Conclusions.
This article has described the low-level operation of Aladdin Knowledge System's HASP dongles in some detail. Particular emphasis was placed on the HASPCODE service due to its pivotal role in the HASP security architecture. A technique and some tips for expediting the determination of the static 64 bit lookup table which fully defines the HASPCODE response for any HASP key were presented.
The information contained in this article is accurate to the best of the author's knowledge, and appertains to the HASP-3, MemoHASP, TimeHASP and NetHASP keys. At the time of writing, it was not yet clear whether it remains applicable to AKS's latest generation of dongles, the HASP4. Investigations into this will be initiated in the near future and reported on as soon as some light is shed on the matter. In this context, it is worth noting that AKS have once again applied that annoyingly presumptuous phrase, "impossible to crack." We shall see.
On the assumption that you, the reader, have at least an intermediate-level grasp of (32 bit) assembly language, you now have the basic knowledge and tools to understand fully the operation of a HASP dongle. How you use this information is, of course, entirely up to you.
:D
Software Emulation of HASP keys
By exefoliator, 2000.
Abstract.
The article which follows explains the operation of the HASP family of software protection keys (dongles) with particular emphasis on the HASPCODE service. Aladdin Knowledge Systems (AKS), the originators and suppliers of this range of products, have made the claim that their keys cannot be emulated in software. This article not only shows this to be a wrong assertion, but also sets out just how to go about collecting information relevant to any HASP key from the executable it is supposed to protect. The reader should note that the information given may not apply to HASP4, the latest generation of keys from AKS. The author will endeavour to establish the nature and operation of the HASP4 keys and report on the findings as they become available.
1. Introduction.
Aladdin Knowledge Systems (henceforth referred to as "AKS") supplies a range of HASP (Hardware against software piracy) keys, commonly known as "dongles," for protecting software from unlicensed use and/or distribution. AKS claims that using HASPs makes programs uncrackable, mainly because of their ASIC (application-specific integrated circuit) chip design. This aspect is purported to be sophisticated to the extent that the hardware cannot be emulated by a general-purpose digital computer, i.e. a Turing Machine.
By my nature, I am mistrustful of all forms of authority, particularly those who profess to have my best interests at heart, while keeping me in the dark about just what knowledge they are privy to that I am not. Telling me that something is not possible without supplying a good and sufficient reason why it should be so is the surest way of getting me to find out for myself. Moreover, despite the current neo-fascist efforts to make reverse engineering illegal (which, if successful, will effectively kill one of the cornerstones of the free market system - by protecting product stasis), it is my belief that it is (a) morally wrong to prohibit it, (b) impractical to enforce such a concept and (c) restrictive to competition and, ultimately, to economic growth. Go figure.
So much for the motivation for writing this article. Now some history. I had one brief encounter with HASP about five years ago when I was tasked with protecting a DOS program with a dongle. I read the AKS literature and decided to use the LPM (linkable protection module) which is a piece of object code one can link into one's program. The LPM provides an interface to all of the HASP calls (more on which later) so that the programmer can decide where and when and how to access the dongle. What worried me was that the LPM for DOS was 27kB in size, and the program I was to protect needed every byte of memory it could scrape together.
So I decided to find out why the LPM is such a hog compared to what it does. Turbo Debug and a day later the answer was plain to see: the LPM does a few useful things from a security perspective - timing traps, hooking debug interrupts, decrypting the memory code image before the call executes and re-encrypting it afterward - but mostly it is full of redundant rubbish which does nothing at all except waste system resources. I rewrote my own LPM in assembler which yielded an object image of just less than 2kB, with the same security measures as in the original LPM.
Recently I again had occasion to evaluate HASP as a protection mechanism. I found that the only thing which had changed in the HASP line during the last five years was AKS's blurb on how uncrackable their product was (see http://www.aks.com/hasp). The ASIC was just the same, the LPM was just the same, except for a 32 bit wrapper and some extra bloat, and the HASP services were just the same. On the strength of this lack of product development, I decided against using HASP.
2. The HASP Product Line and HASP services.
The HASP product line consists of several keys. The most basic is HASP-3, followed by MemoHASP-1, MemoHASP-4, TimeHASP and NetHASP. Each of these more sophisticated keys provides some read/write memory (between 112 and 512 bytes) and a unique 32 bit HASP identification code. The memory can be read/written in individual 16 bit words or in contiguous blocks. In addition, TimeHASP incorporates an on-board real time clock that can be set or queried. NetHASP can be used over networks to control the number of simultaneous users of a program.
HASP-3 supports only three basic services, i.e. ISHASP (reports whether a HASP key is connected to the computer or not), HASPSTATUS (reports the HASP key type) and HASPCODE (returns a 64 bit code for a given 16 bit seed code). The other keys also support these services but include extended services for reading/writing HASP memory, reading the HASP ID code and accessing the on-board clock, as applicable. The assembler calling conventions for each of these extended services can be found in the HASP documentation included with the developer's kit.
At base, the three services listed above use the 16 bit calling conventions given below, even in a Win32 environment:
Service: Call: Return:
ISHASP BH <- 1, BL <- LPT no. AX = 0 (no HASP) or 1 (HASP found)
HASPCODE BH <- 2, BL <- LPT no. AX = return code 1
AX <- seed code BX = return code 2
CX <- password1 CX = return code 3
DX <- password2 DX = return code 4
HASPSTATUS BH <- 5, BL <- LPT no. AX = memory size
CX <- password1 BX = HASP type
DX <- password2 CX = actual LPT no.
With the exception of HASPCODE, the principles of operation of the above services are easy to understand and hence emulate in a fairly obvious and straightforward way. This is also true of the extended services. It is therefore no surprise that the HASPCODE service is the foundation upon which almost all of HASP's security is based, and it is the operation of just this service that will be discussed in detail here.
3. HASP Security Measures.
Before dealing with the low-level details of HASPCODE, a little needs to be said about some of the measures taken by AKS to enhance software security and protection. The three most important of these are virus/tampering detection, the Pattern Code Security (PCS) facility and the HASP envelope.
Virus/tampering detection is a relatively standard cyclical redundancy check (CRC) run on (a portion of) the executable's disc file image. The CRC signature(s) must match a set of hard-coded ones. The routine that performs this check can be found and disabled in fairly short order by comparing program flow between successful and failed CRCs.
Pattern Code Security is a facility which updates up to 25 instances of a predefined HASP data structure with a single call to the HASP routine. These structures are static and can live anywhere in the executable's memory space (provided the memory is writable). The exact layout of a PCS structure is defined in the HASP developer kit, but it includes an ANSI string signature ('$HASP$PCS'), the service, the service parameters and space for the return values. All of the PCS structures can be found by finding the PCS masterlist. This is a structure with the ANSI string signature '@SCP@PSAH', followed by the number of PCS entries (word) and a list of 32 bit offsets to these structures. Note that this masterlist will not be found in the disc image of the executable if the HASP envelope was used to protect the program since it is encrypted. Once again, given the address of the PCS masterlist, PCS emulation is not especially difficult.
Finally, and probably most importantly, the HASP envelope provides software security that is "virtually impossible to crack," according to AKS's blurb. The basic idea is to encrypt most of the executable using the HASPCODE service to obtain an encryption key and to decrypt the program once it is loaded into memory, provided the proper HASP key is attached to the computer. The idea itself seems to make sense and appears to be logically sound until one subjects it to closer scrutiny. As will be described later, the HASP envelope divulges two vital pieces of information about the HASP key, one of which will be instrumental in emulating the key in software. The only really important aspects here are that (a) the HASP routine usually occurs twice in the executable - once for the envelope and again for the executable itself, and (b) the breakpoints for intercepting HASP calls can only be set after the decryption routine(s) are complete.
It should be noted that the envelope can be applied to an executable more than once. In this case a separate HASP routine exists for each envelope, but this does not make the program significantly more secure because the en/decryption scheme is the same for each envelope with only the crypto keys being different. As we shall see, getting these keys is a cinch once the outermost envelope is penetrated.
All of the cryptography routines used by AKS in the envelope that I have seen are 64 bit block ciphers - i.e. a 64 bit mask is generated and the target bytes are XORed to en/decrypt them. Nothing new there. The tricky bit is finding the first 64 bit block, which is got from the HASP key via the HASPCODE service. The envelope, like all Win32 applications, is bound by the conventions of good behaviour, and that presents perhaps the biggest chink in AKS's armour - the HASP envelope mustn't raise any exceptions or faults.
The decryption routine usually proceeds as follows:
1. A preliminary decryption routine, which does not depend on any HASP key data, is run on a block of memory to decrypt the envelope's HASP and main decryption routines.
2. The decrypted HASP routine is called with the ISHASP service to check whether any HASP key is attached to the computer.
3. If a HASP key is attached, the routine is then called with the HASPCODE service and a fixed seed code.
4. The HASPCODE return values are then compared directly with a set of expected values hard-coded into the envelope. No attempt is made to hide, encrypt or spread this comparison.
5. If the return values agree with the expected ones, the envelope calls the HASP routine again, requesting the HASPCODE service with a different seed code. The return values are used to prepare a 64 bit decryption key. The envelope then performs the main decryption and thereafter passes control to the executable.
It is at steps (3) and (4) that a great deal of valuable information is gleaned. By checking that the correct key is attached, the envelope divulges the two 16 bit passwords for the dongle and, more importantly, a complete set of return codes for a specific seed code. Later I will show how the latter fact reduces HASP's cipher strength from 64 bits to between 20 and 25 bits, putting it well within the limits of a brute-force attack. To be completely accurate, the cipher strength is actually only 16 bits because the seed code is only 16 bits - i.e. there are at most 65536 (= 2^16) unique 64 bit return code sequences. Thus, the envelope "protection," as implemented by AKS, does quite the opposite of what it was intended to do.
Before analysing the cryptography aspect further we need to know how the HASPCODE routine works.
4. The HASPCODE Algorithm.
The algorithm by which the HASP key generates return codes is surprisingly simple - a linear-congruential pseudo-random number generator is used to obtain a 6 bit (0 to 63) offset into a static 64 bit lookup table, and the return code is generated one bit at a time. The following assembly listing shows the complete ISHASP and HASPCODE emulation routine for a 32 bit environment.
80 FF 01 cmp bh,1 ; ISHASP requested?
75 05 jne @CheckTwo ; jump if not
31 C0 xor eax,eax ; else...
40 inc eax ; ...return eax = 1...
EB 67 jmp @EndHProc ; ...and exit
@CheckTwo:
80 FF 02 cmp bh,2 ; HASPCODE requested?
74 0A je @MakeCode ; jump if so
EB 60 jmp @EndHProc ; else do nothing
@HASPData:
db x0 x1 x2 x3 x4 x5 x6 x7 ; HASP static data
@MakeCode:
31 C9 xor ecx,ecx ; ecx <- 0 bit offs
31 DB xor ebx,ebx ; ebx <- 0 byte offs
E8 00 00 00 00 call @NextInst ; push next addr...
@NextInst:
5E pop esi ; ...and save to esi
@NextHBit:
53 push ebx ; save byte counter
66 BB 89 19 mov bx,1989h
F7 E3 mul eax,ebx
83 C0 05 add eax,5 ; seed <- seed*1989h+5
0F B7 C0 movzx eax,ax ; seed <- seed and FFFFh
50 push eax ; save seed
C1 E8 09 shr eax,9
80 E0 3F and al,3Fh ; al <- bit offset
89 C3 mov ebx,eax
C1 EB 03 shr ebx,3 ; ebx <- byte no.
80 E0 07 and al,7 ; al <- bit no.
51 push ecx ; save current code
88 C1 mov cl,al
B0 01 mov al,1
D2 E0 shl al,cl ; al <- bit mask
84 44 33 EF test byte ptr [ebx+esi-11h],al ; is bit set?
B0 00 mov al,0 ; assume not so
74 01 je @BitClear ; jump if clear
40 inc eax ; else set bit
@BitClear:
59 pop ecx ; ecx <- code string
D0 E5 shl ch,1 ; move to next bit
08 C5 or ch,al ; add latest bit
58 pop eax ; eax <- seed
5B pop ebx ; ebx <- byte count
41 inc ecx ; inc bit count
80 F9 08 cmp cl,8 ; 8 bits done?
75 C7 jne @NextHBit ; jump if not
F6 C3 01 test bl,1 ; byte count odd?
74 03 je @EvenByte ; jump if so
5A pop edx ; get ret code
88 F1 mov cl,dh ; add upper 8 bits
@EvenByte:
51 push ecx ; save ret code
31 C9 xor ecx,ecx ; clear code string
43 inc ebx ; inc byte count
80 FB 08 cmp bl,8 ; 8 bytes done?
75 B6 jne @NextHBit ; jump if not
5A pop edx ; edx <- ret code 4
59 pop ecx ; ecx <- ret code 3
5B pop ebx ; ebx <- ret code 2
58 pop eax ; eax <- ret code 1
@EndHProc:
C3 ret ; return to caller
The above code is tailored to be space efficient and to work regardless of where in memory it is loaded so that there is no need for any further address checking. It follows the machine code calling protocol for the ISHASP and HASPCODE services, as outlined earlier. Also, the code bytes are shown to reflect the actual size (114 bytes) and the signature of the emulation routine on an 80386 or later.
The only difficulty in applying the above to a given HASP key lies in finding the 64 bits of static lookup data (the 8 bytes appearing as x0 to x7 in the above listing). This is where the information previously collected from the envelope is very useful, and leads us into the next section.
5. Reconstructing HASP's Static Table.
The basic idea behind reconstructing the 64 bits of static HASP data is to recreate the sequence of pseudo-random bit offsets for the known seed code and extracting the corresponding bits in the known return codes, so building, at least in part, the lookup table for the dongle.
The procedure is as follows - given Seed and RetCode_1 to RetCode_4:
(1) write the four 16 bit return codes in order as a sequential bit string of 64 0s and 1s, not forgetting about byte reversals on 8086 processors, i.e. as LSB(RetCode_1) MSB(RetCode_1) LSB(RetCode_2) ... LSB(RetCode_4) MSB(RetCode_4)
(2) Construct a 64 character string of '?' - this will become the HASP static table bit string
(3) Let CurrentBitNo <- 0
(4) Let Seed <- (Seed*0x1989h+0x5h) and 0xFFFFh and BitOffs <- (Seed shr 0x9h) and 0x3Fh
(5) Let BitOffs <- 7+((BitOffs shl 1) and 0x70h)-BitOffs
(6) Get bit no. CurrentBitNo (from left to right, zero-based) from the bit string of the return codes in (1)
(7) Set the character in the string under (2) at position BitOffs (left to right, zero-based) to '0' or '1', in accordance with the state of the bit referred to in (6)
(8) CurrentBitNo <- CurrentBitNo+1
(9) if (CurrentBitNo < 64) then go to step (4) else stop
Once finished, the above routine yields a string with 0s, 1s and ?s. The ?s are those bits which were not referenced by the seed code, while the other bits are as they appear in the HASP key's static table in the correct order, viz. x0 x1 ... x7, when subdivided into groups of eight bits.
Generally, a single set of HASPCODE data composed of a seed code plus four return codes will reveal between 39 and 44 bits, leaving 20 to 25 unknown. A different seed code for which the return codes are unknown will reference between 14 and 19 of the unknown bits. At worst, these bits can be found by a brute-force search. However, reasonable guesses can be made as to the state of these bits because the static data exhibits some patterns and cycles that only become evident when it is represented as an eight-by-eight bit matrix. As an example, the bit matrix for the Memo/Time/NetHASP DEMOMA demo key has the following bit matrix:
x0 = 0 0 1 1 1 0 0 1 = 39h
x1 = 0 1 1 0 0 0 0 1 = 61h
x2 = 0 0 1 1 1 0 1 1 = 3Bh
x3 = 0 1 1 0 0 0 1 1 = 63h
x4 = 1 0 0 1 1 1 0 1 = 9Dh
x5 = 1 1 0 0 0 1 0 1 = C5h
x6 = 1 0 0 1 1 1 1 1 = 9Fh
x7 = 1 1 0 0 0 1 1 1 = C7h
Now suppose that we do not know this HASP key's static table, but that we have learned that for the seed code 11073 (decimal) this HASP key returns the code set
RetCode_1 = 14990 = 3A8Eh
RetCode_2 = 23754 = 5CCAh
RetCode_3 = 43929 = AB99h
RetCode_4 = 45507 = B1C3h
Upon applying the procedure given above, we obtain
[ 8E ] [ 3A ] [ CA ] [ 5C ] [ 99 ] [ AB ] [ C3 ] [ B1 ]
BitString(1) = "10001110 00111010 11001010 01011100 10011001 10101011 11000011 10110001
"OutString(9) = "00111001 01?000?? ?0?????1 0?1?0??1 1??1110? 1?0?0?01 100?11?? 1?000?11"
whence
x0 = 0 0 1 1 1 0 0 1 = 39h
x1 = 0 1 ? 0 0 0 ? ? = 40h, 41h, 42h, 43h, 60h, 61h, 62h or 63h
x2 = ? 0 ? ? ? ? ? 1 = (one of 64 possibilities)
x3 = 0 ? 1 ? 0 ? ? 1 = (one of 16 possibilities)
x4 = 1 ? ? 1 1 1 0 ? = 9Ch, 9Dh, BCh, BDh, DCh, DDh, FCh or FDh
x5 = 1 ? 0 ? 0 ? 0 1 = 81h, 85h, 91h, 95h, C1h, C5h, D1h or D5h
x6 = 1 0 0 ? 1 1 ? ? = 8Ch, 8Dh, 8Eh, 8Fh, 9Ch, 9Dh, 9Eh or 9Fh
x7 = 1 ? 0 0 0 ? 1 1 = 83h, 87h, C3h or C7h
In this case 24 bits remain indeterminate, but inferring bit values based on assumed patterns reduces this number. For example, the first column has one unknown bit in the third row and the bits appear to be composed of two groups of four. It is therefore reasonable to suppose that the unknown bit is a 0. Similarly, the unknown bit in column 5, row 3 is probably a 1, and the three unknown bits in column 8 are all likely to be 1s. Continuing in this way, other patterns can be sought and built upon to yield a few of the most likely bit matrices, each of which can then be tested either against the HASP envelope or against other HASPCODE requests until the correct candidate is found.
Having picked out the bit patterns for seven different HASP keys (the bit patterns and serial/batch numbers of which I will not divulge here for obvious reasons), it is safe, and indeed profitable, to infer the unknown bits of other keys, using the above example as a rough guide. One should look mainly for horizontal and vertical cycles and patterns, but diagonals can also prove useful, especially if the entire bit matrix is copied immediately to the right as well as below itself. Similar groups of two and four bits recur in all sorts of fairly self-evident ways.
With some trial-and-error, the remaining bits can normally be found in fewer than about 30 attempts.
Does AKS's claim of unsurpassed security still hold water in light of the above? The answer is, I think, quite obvious.
6. Completing the HASP Emulation.
With the knowledge that it is possible to construct a short piece of code that performs exactly the same function as the ISHASP and HASPCODE calls to the HASP routine, emulating the remaining services is almost trivial. Routines that do what the READMEMO, WRITEMEMO, HASPSTATUS, HASPID, READBLOCK and WRITEBLOCK services do can be coded in a straightforward way, making use of the main program's code space to read/write HASP "memory," if necessary. Of course, this is only required if the main program actually makes any of these calls to the HASP routine. Some difficulty may be experienced in the case where a TimeHASP is used and its on-board clock is accessed, but even this can be overcome by using the GetSystemTime() Win32 API function, if required.
Once a complete list exists of the HASP services a given program makes use of, a complete HASP emulation routine can be cobbled together without too much difficulty from the various bits and pieces. If the program makes use of PCS (see Section 3.), the emulation routine can be called from a loop which loads, executes and saves the PCS specifications sequentially from the data in the PCS masterlist and the PCS structures this list identifies.
By reversing the relevant decryption routines and adjusting any CRC or checksum routines, an executable can be patched so as to not require an actual HASP key. This can generally be achieved with a total patch size not exceeding 1kB. This may sound like a lot, but pales in comparison to the 32 bit HASP routine which is around 70kB in size - another victim of code bloat?
In some versions the block cipher mask depends on the en/decrypted bytes of the preceding en/decrypted block. This is a further safeguard against patching but is not particularly effective since the blocks immediately before and/or after the patch can be padded with adjusting bytes to ensure that other code and data are decrypted correctly.
7. Conclusions.
This article has described the low-level operation of Aladdin Knowledge System's HASP dongles in some detail. Particular emphasis was placed on the HASPCODE service due to its pivotal role in the HASP security architecture. A technique and some tips for expediting the determination of the static 64 bit lookup table which fully defines the HASPCODE response for any HASP key were presented.
The information contained in this article is accurate to the best of the author's knowledge, and appertains to the HASP-3, MemoHASP, TimeHASP and NetHASP keys. At the time of writing, it was not yet clear whether it remains applicable to AKS's latest generation of dongles, the HASP4. Investigations into this will be initiated in the near future and reported on as soon as some light is shed on the matter. In this context, it is worth noting that AKS have once again applied that annoyingly presumptuous phrase, "impossible to crack." We shall see.
On the assumption that you, the reader, have at least an intermediate-level grasp of (32 bit) assembly language, you now have the basic knowledge and tools to understand fully the operation of a HASP dongle. How you use this information is, of course, entirely up to you.
:D
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
他的文章
- [分享]一个建站软件的注册算法分析(最新版) 12632
- AutoCAD七天超级速成法2.0版 算法分析笔记 9870
- 随想桌面变身V3.1.2005.0328算法分析 7736
- 智能开关机 V3.20算法分析笔记 9419
- DELPHI编程宝典0402阅读密码分析笔记 9199
看原图
赞赏
雪币:
留言: