SSI Win32 Dongle Protection
Initial workaround for difficult Win32 targets

22 January 1998

by Spyder


Courtesy of Fravia's page of reverse engineering

slightly edited
by fravia+

There is a crack, a crack in everything That's how the light gets in.

Rating

( )Beginner (x)Intermediate (x)Advanced ( )Expert

This essay describes a method to work around a rather awkward dongle protection for Win32 programs using Sentinel dongles.


SSI Win32 Dongle Protection
Initial workaround for difficult Win32 targets
Written by Spyder

Introduction

This isn't a complete crack of anything, just a workaround which makes a seemingly impossible crack possible. I know little about dongles, or about the companies that make them. For this target the company is called "Sentinel", and if you see references to Sentinel, or SSI, then we are talking about the same thing. If you tried for a dead listing and found megabytes of garbage that's also a good (or bad?) sign.

I know little about the actual dongle code - never had to look at it - setting breakpoints on printer port I/O or something is the *last* way to start cracking any dongle-protected (aka dongled) program (IMHO).

Tools required

Borland Turbo debug.
Borland Tdump is handy.
A good binary editor
A good disassembler (can you say IDA?).

Target's URL/FTP

There is no specific target.

Essay

Why was this crack going to be so impossible? Well the dongle code is a wrapper for a semi-protected executable. When the wrapper is applied it encrypts most of the executable code, in fact it attaches to the end of the program and takes over the program entry point. When the program is run, the dongle code gets control, and if it is happy with the dongle it will decrypt the executable and pass control to it. The executable can then call back into the dongle code to make whatever specific dongle checks it wants, in order to confirm that the dongle is still present at various times during execution.

So even if you crack the initial dongle code you will still need to understand and patch the main executable. The encryption makes understanding difficult and patching almost impossible. Fully cracking the dongle code is unlikely to be an option. These types of dongles do things like returning a value from a variable string passed to them, the algorithm and keys to do this are actually sitting inside the dongle - there is no easy way of patching the dongle driver code to get the same result.

I haven't looked at the exact details of the main encryption, but it isn't weak. I'm pretty sure it is a rolling encryption where changing one byte will affect all following bytes which is why patching is so hard. I am also pretty sure that the encryption keys come out of the dongle (they should work this way if the dongle suppliers have any sense) which means you are going to need the dongle at least for a couple of hours, but, you might also have SOME LUCK. "Cracking without luck it's as impossible as cracking without feeling".

If the program has a demo mode, or perhaps some kind of option for network licensing, then it has to be decrypted without the dongle present, so it can run and then decide if it really needs a dongle or a network license or whatever in order to run fully. So, knowing this, how do we work around it?. My approach is to undo what was done when the wrapper was applied, re-creating something near enough to the original program before it was wrapped, so that normal reversing techniques can be used. Lets look at some interesting bits of a tdump for a sample program :-

Entry RVA     0003D950
Image base    00400000

Object table:
#   Name      VirtSize    RVA     PhysSize  Phys off  Flags
--  --------  --------  --------  --------  --------  --------
01  .text     0001E200  00001000  0001E200  00000400  E0000020 [CERW]
02  .rdata    00000800  00020000  00000800  0001E600  C0000040 [IRW]
03  .data     00013E24  00021000  0000D200  0001EE00  C0000040 [IRW]
04  .PAD000   00000E00  00035000  00000E00  00000000  C0000080 [URW]
05  .SSINod   00007A00  00036000  00007A00  0002C000  E0000040 [IERW]
06  .rsrc     00000800  0003E000  00000800  00033A00  C0000040 [IRW]
07  .idata    00000C00  0003F000  00000C00  00034200  C0000040 [IRW]
08  .reloc    00000200  00040000  00000200  00034E00  42000040 [IDR]

SSINod is the added dongle code, PAD000 is where the original program .reloc segment (which contains import addresses patched up during program load) was. Image base is where the program gets loaded in a flat memory model (most addresses are relative to this base). Entry RVA is the program start address. The next step is to get the program loaded and decrypted and into Turbo debug. If the program runs and stays running while it presents an error message then the best operating way is to fire up TD32 in a dos window and attach to the program. One of the wonders of Win32 debugging, just pick File | Attach and select the window, after a while TD32 will get control of the program and you can look around.

You might also be able to start the program with TD32 but getting control once it has run and decrypted isn't so easy. You may even be able to trust the memory image when the program has terminated or maybe look at the stack and find some locations where a hardware breakpoint can be set. I'm sure it's a lot easier with SoftICE but SoftICE (as far as I know) can't do what TD32 is about to. Anyhow I assume you somehow managed to get control of the program using TD32. Have a look at the image base + .text RVA and make sure it looks like code, to confirm that the program was decrypted, if not then maybe you need to borrow a dongle from some friends and start again.

Now open up a dump window, go to (Ctrl-G) the image base address and start marking a block by holding shift and dragging the mouse from the required hex byte (block marking in TD32 is a bit strange and shift cursor keys don't always work). Now go to the end of the .text segment (which should extend the marked block). Select Block | write (Ctrl-B, W) and TD32 will kindly dump the executable header and whole decrypted text segment to a file for you. TD32 seems to write such blocks at about 1-2kB per second, which is pretty slow, so you might take a coffee/cocktail break. (Sorry if I am being too specific about "driving TD32" but the first time I did this I didn't realise TD32 could dump memory blocks to disk and I spent hours writing memory dump 'panes' to the log file and then I wrote a whole own-made program to turn the dumps back into binary, so I want to make sure that my readers will know this trick - a little knowledge sometimes goes a long way).

Now one more thing before we are done with TD32. Take a look at the PAD000 segment. It will be mostly zeros and you will see a block of 32 bit addresses. These are the import entries which were patched up at load time in the .reloc segment and part of the dongle code copies them here, where the main executable expects them to be. The order and number of entries is not the same as in the .reloc segment so you can't do a simple copy of it, nor fiddle with RVAs to get them in the right place. Set an hardware memory write breakpoint on any of these locations and then restart the program from TD32 (Ctrl-F2). TD32 should break in the middle of the dongle code which is copying across these entries. Take a look at the code and - if it is like the stuff I have seen- you will find a table of pairs (couples) of 32 bit addresses pointed to by EBX. These are pairs of relative (to image load) addresses inside .reloc, with the required relative address in .PAD000 for the same import. Mark this table and dump to disk as before.

Now we are done with TD32 and -probably- with the dongle.

Putting it back together

Now we have to insert the decrypted dump back into the executable, which is really just binary editing of a sort, but rather tricky. Take another look at the tdump object table and understand what it means...

Entry RVA     0003D950
Image base    00400000

Object table:
#   Name      VirtSize    RVA     PhysSize  Phys off  Flags
--  --------  --------  --------  --------  --------  --------
01  .text     0001E200  00001000  0001E200  00000400  E0000020 [CERW]
02  .rdata    00000800  00020000  00000800  0001E600  C0000040 [IRW]
03  .data     00013E24  00021000  0000D200  0001EE00  C0000040 [IRW]
04  .PAD000   00000E00  00035000  00000E00  00000000  C0000080 [URW]
05  .SSINod   00007A00  00036000  00007A00  0002C000  E0000040 [IERW]
06  .rsrc     00000800  0003E000  00000800  00033A00  C0000040 [IRW]
07  .idata    00000C00  0003F000  00000C00  00034200  C0000040 [IRW]
08  .reloc    00000200  00040000  00000200  00034E00  42000040 [IDR]

The .text segment RVA is 1000 which means that in memory this is started at the image base + 1000 = 401000. The Phys off is where .text lives in the executable file. That means the decrypted dump has c00 bytes between 400 and 1000, which needs to be cut. The executable file header is not encrypted, and in my experience it is always identical in the memory image. With your favourite editor, hack out the c00 bytes and paste the dumped image on top of the executable.

Fixing the imports

We have this dump of pairs of addresses to move import addresses between .reloc and .PAD000 segments at start up. We should have plenty of space for code in the .SSINod segment as once the program is really cracked it shouldn't ever have to call inside there. I turned the binary dump into hex, then manually edited it into db assembler statements for any old assembler that can create a binary output file. Add A1h in front of the first address for a mov eax,[????] and A3h in front of the next for a mov [????],eax. Remember to add the Image load address to all addresses as they are also relative. Double check that you are loading from .reloc and storing into .PAD000.

I just dumped this binary fragment into the executable at the start of the .SSINod segment. You might then want to patch up the Entry RVA in the executable header to point at this code (or patch a jump in later).

Where are we now?

At this stage you should have an executable that has no encryption and will start running a lump of code that moves import addresses to where the original program expects them. The entry point probably points at this code but don't run it as the import patch is currently followed by dongle garbage. The executable should be exactly the same size as the original - if not I'm afraid you messed up the editing somewhere. Now you have something you can start cracking, fine for dead listing, but you will have to find the original program entry point and patch inside it a jump at the end of the import patching code before you can try to run it.

If you are using IDA it may well discover the program entry point as a standard runtime library startup function although IDA seems not to automatically recognize the compiler in these reconstructed executables (select a signature manually). The other alternative is to debug another program built with the same tools to find what the entry point code looks like. You could even search for likely looking code, startup code will anyway probably call GetVersion pretty quickly. Once you have found and patched inside the original program entry point, the workaround is done. You have now an executable with the same function as the original before it was wrapped.

The chances are that this program will still call into the dongle code but as the authors expected the dongle wrapper to make examination difficult and patching impossible they are unlikely to have done anything too sophisticated, and if they have, that suits us well: even more challenges!.

Final Notes

Well I hope you found this interesting and perhaps useful. I haven't seen this particular Sentinel dongle encrypt anything more than the .text segment, indeed, it doesn't always encrypt the whole text segment. The same recovery technique could be used for other encryption based schemes but things get tricky where more than the .text segment is encrypted. You would have to break and capture an image before the program runs enough to 'smudge' its own data segments, the .reloc segment is particularly painful as the OS 'corrupts' that during loading (however, this kind of stuff is equally difficult for the protectors).

The HASP protection sounds quite similar to my Sentinel one. I suspect the Sentinel rolling encryption is more robust.

Spyder