The HASP Envelope

Target :- Customer.exe (included with Cimagrafi v5.07+).

If you are at all au fait with HASP you will know that the protection takes 2 forms, the API through which hasp() services are actioned and Aladdin's fully blown PE encryptor known as the envelope, which relies on response codes from the dongle to do the decryption. I chose Customer.exe for 2 reasons, firstly its an enveloped target as good as any other, and secondly we are here to learn about reverse engineering as opposed to stealing Cimagrafi (a rather good design program which is sadly protected by a HASP).

HASP's envelope checks for the presence of SoftICE using a very familiar trick, CreateFileA for the video driver //./SIWVID, however before we start delving into vast amounts of code I recommend that you PEDump the target.

Section Table
  01 .text     VirtSize: 00020AEE  VirtAddr:  00001000
    raw data offs:   00001000  raw data size: 00021000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: 60000020
      CODE  MEM_EXECUTE  MEM_READ

  02 .rdata    VirtSize: 00006194  VirtAddr:  00022000
    raw data offs:   00022000  raw data size: 00007000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: C0000040
      INITIALIZED_DATA  MEM_READ  MEM_WRITE

  03 .data     VirtSize: 00006908  VirtAddr:  00029000
    raw data offs:   00029000  raw data size: 00003000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: C0000040
      INITIALIZED_DATA  MEM_READ  MEM_WRITE

  04 _TEXT_HA  VirtSize: 0000FC88  VirtAddr:  00030000
    raw data offs:   0002C000  raw data size: 00010000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: C0000040
      INITIALIZED_DATA  MEM_READ  MEM_WRITE

  05 .rsrc     VirtSize: 0000E258  VirtAddr:  00040000
    raw data offs:   0003C000  raw data size: 0000F000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: 40000040
      INITIALIZED_DATA  MEM_READ

  06 .protect  VirtSize: 000275F4  VirtAddr:  0004F000
    raw data offs:   0004B000  raw data size: 00028000
    relocation offs: 00000000  relocations:   00000000
    line # offs:     00000000  line #'s:      00000000
    characteristics: E0000020
      CODE  MEM_EXECUTE  MEM_READ  MEM_WRITE
Note that _TEXT_HA is the HASP API section, we'll see this later, and .protect the actual envelope, sometimes these sections have their names changed (e.g. .protectzz) yet the envelope always equates to something around 160k. As you might expect the envelope is heavily encrypted (disassembling will give you about 300h of the initial round of decryption which works on the principal of sliding an XOR key through many small rounds of code and then calling the newly decrypted routines instantly).

The decryption is a real mess and there is nothing to be gained in studying it, in fact tracing it too low in SoftICE will result in a horrid crash. What the envelope subsequently does is call its own decrypted hasp() routine to check for a specific dongle before using the response codes to decrypt and hand control to the original program. The first step is to kill the anti-SoftICE trick I described above, after which you should bpx for FreeEnvironmentStringsA which is called in most 32-bit HASP protections immediately after the CALL to the main hasp() routine. Here's how it looks :-

0137:0045A8E5 CALL [EBP+00]  <-- This is hasp() for real.
0137:0045A8E8 PUSHAD  <-- Save all registers.
0137:0045A8E9 LEA ESI,[0045E1B5]
0137:0045A8EF PUSH DWORD PTR [ESI]
0137:0045A8F1 LEA ESI,[KERNEL32!FreeEnvironmentStringsA]
0137:0045A8F7 CALL [ESI]
0137:0045A8F9 POPAD  <-- Restore registers (you break here).
0137:0045A8FA RET

0137:004587E4 PUSH EBP  <-- Now set a bpx here.
0137:004587E5 CALL 0045A73F  <-- Highest level hasp().
0137:004587EA POP EBP
When you return the hasp() envelope routine has just been executed for its first time, its always IsHasp, so set EAX=1 and bpx for the next service. Sure enough the next service is 2 (in BH), ECX holds vendor password 1, EDX vendor password 2 & EAX the seed code. In this case we have EAX=1DFE, ECX=2459 & EDX=2CF3, after the call to hasp() again EAX through EDX = 0, they should actually be filled with the required response codes for the given seed. In all Win32 envelopes I've seen its been possible to recover these responses by simple tracing (not that we actually need to).

Using the HaspCode algorithm we can find the desired responses, so patch in EE2D, 8F91, 8F9F & ED0F, now F5 and you'll get the check repeated, patch in the responses once again. The 3rd check decrements the seed code by 1 so you need to patch in DBA5, BF7E, 1FDA & 62DB (returns for seed 1DFD). It is these values that will be used for the decryption. I recommend you now disable all the breakpoints and bpx for VirtualAlloc, in different versions of the envelope WriteProcessMemory is also extensively used. After this breaks, place the data window at the RVA of the first section (its usually 401000), now watch slowly, the idea of un-enveloping is as follows :-

i) get the raw sizes and offsets of the decrypted sections and dump them to files (I cannot recommend IceDump enough for this purpose).

ii) establish the location of the import table and real entry point.

Tracing slowly through should alert you immediately as to what happens, VirtualAlloc is used to allocate an area of memory (1000 or 8000 usually), the various sections are then committed and decrypted, just trace slowly through this until you can see the critical code.

0137:00453549 MOV EAX,[EBP-0C]  <-- Size of allocated memory (0x8000).
0137:0045354C PUSH EAX  <-- Push it on the stack for function.
0137:0045354D MOV EAX,[EBP-08]  <-- VirtualAlloc area.
0137:00453550 PUSH EAX
0137:00453551 MOV EAX,[EBP-24]  <-- RVA of area being allocated.
0137:00453554 PUSH EAX
0137:00453555 CALL 00453830  <-- Committ section and write encrypted data to it.
0137:004535B5 CALL 0045362B  <-- Now decrypt it.
At the end of this loop you should have noted the following details and have dumped the following :-

Section     RVA      Size    Raw Location    Size in bytes
----------------------------------------------------------
.text     x401000   x205B3       x1000          132,531
.rdata    x422444   x45DF	x22444           17,887
.rdata    x426B78   x15E3       x26B78            5,603
.data     x429000   x2F85	x29000		 12,165
_TEXT_HA  x430000   xFA01	x2C000		 64,001
You can now paste these decrypted files over the original bytes, but go very careful when translating the _TEXT_HA RVA into a raw offset in the actual file. If you are doing this part with UltraEdit, I advise you take it slowly to avoid errors.

Fixing up the PE

This will be the important stage before we hopefully kill the .protect section altogether, lets firstly fix up those things we must, the entry point and import table. The easiest way to find these is to unpack the sections then set a bpx for GetProcAddress or SetTimer. I've always found that this provides a close enough point to recover the Entry Point RVA, Import Table RVA & size (if applicable), also the .reloc RVA & size (if applicable).

0137:0045476E MOV EAX,[EBP+08]  <-- Real Entry Point (00407EAA).
0137:00454771 PUSH EAX
0137:00454772 POP ESP
0137:00454773 POPAD  <-- Maybe worthwhile as a search string.
In this case (which is somewhat anomalous), the import table isn't unpacked to its own dedicated section, instead portions of it span both of the .rdata sections we dumped earlier (this means you must redump and paste the entire .rdata section at the entry point 00407EAA, because GetProcAddress is used after the decryption to reconstruct the import table). The other Cimagrafi programs do have dedicated .idata & .reloc sections which means simple fixing in the header will suffice. You can now change the entry point to 00407EAA and our newly decrypted customer.exe will run (you must also decrypt the .protect section because our import table points too it).

Now you should now be able to get customer.exe into IDA, with only a few errors (all in .protect unsurprisingly). You'll also get a lot of library recognition. If you've followed my instructions carefully you can proceed to reverse the protection as normal (services 1, 5,6 & 50). Because of the anomalous nature of this target I described above it isn't actually worthwhile removing the .protect section, although in all of the other Cimagrafi targets it certainly is. There also is actually a good reason to crack this target, intercepting service 50 will allow you to discover a lot about various words and bytes in the dongle :-).

As a general rule when decrypting the HASP envelope, find i) the unpacking routines, ii) the encrypted sections and iii) the import table loop & entry point, once at the entry point you can dump everything and then apply the necessary PE fixups. This should mean that for this type of HASP envelope (version 6/6.1) reconstruction should take no longer than 5 minutes.

You have finished reading another tutorial courtesy of CrackZ's Reverse Engineering Page.
Find a quick way back to more documents with this link.

Return to Main Index, Dongles.


© 1999 CrackZ. 4th October 1999.