Cracking Rightime

Encryption and checksums in a small DOS utility

Not Assigned
20 March 1998
by Red Lantern
Courtesy of Fravia's page of reverse engineering
A good essay by Red Lantern, I just hope to receive MANY MORE essays that deal with DOS com programs... for many reasons:
1) real reversers (should :-) know how reverse any operating system anyway;
2) older dos programs ('Abandoned-warez') that nobody uses anymore often hide jewels of programming (and protection :-) art inside, as +ORC always warned us;
3) disassembling and reversing a COM file is an experience 'per se', and I'm sure you'll LOVE it once you get the grasp of it...
And you'll surely get the right 'feeling' reading this very well structured essay by Red Lantern, a new contributor that I hope will send many more essays like this one: good prepared, filled with intuition and careful explanation. You'll use old (almost forgotten) nice, succulent session for any +cracker!
One last word: reading this essay is pretty interesting per se, yet if you have a little time, fetch the target and work a little LIVE with old on it, great fun, and moreover you'll see how many surprises this old 'swiss knife' of the cracker still has to offer!
There is a crack, a crack in everything That's how the light gets in
( )Beginner (x)Intermediate ( )Advanced ( )Expert

An useful essay for beginners and intermediate reversers to see how to defeat encryption and checksums using a combination of the live approach using the now almost forgotten DEBUG.COM and the "dead-listing" approach.  A 90-day shareware expiration is trivial to remove after the encryption is removed.

Cracking Rightime
Encryption and checksums in a small DOS utility
Written by Red Lantern


As +OCR and Fravia constantly remind us, sometimes the best opportunities for learning come from studying small, old-fashioned DOS programs. This small little 28k DOS TSR program corrects your PC's clock. Like many shareware programs, it stops working after 90 days. Although removing the 90 day limit is trivial, we will learn about sidesteping encryption and checksums to reach the 90 day limit code.  

The shareware version of this program always prints verbose startup information.  The registered version allows the program to start up silently. We will look at a relatively easy way to add the silencing function to this program, even though the program does not provide for a means of registration.

Tools required

Good old DOS  Basic knowledge of using debug is assumed.

A DOS disassembler (I used IDA 3.7, freeware version)
Target's URL/FTP

version 5.06

Program History

Current version 5.06 for DOS.


We start by disassembling the file. We scan through the listing for strings about registration, expiration, etc. We find no strings. Sometimes this may merely mean that the disassembler has flaked out on us. Look using a hex editor. Still no strings, of any kind. This is a definite sign of an encrypted file. Hmmmm.

Encrypted files cannot initially cracked using the "dead-listing" approach. However, the "dead- listing" approach can give us a look at the decryptor code. The encrypted program portions will disassemble into garbage. The basic approach to cracking encrypted files is to run them under a debugger until the program has decrypted itself. At this point, we stop and save the decrypted image to disk and begin the usual cracking process. Unfortunately, this is difficult to do with EXE files. With old-style COM files like, our old friend DEBUG, now mostly forgotten, makes this easy, with its ability to write arbitrary areas of memory out to disk

We run our program through a disassembler and scan the listing, following the order of execution.. We notice something interesting at 3B3D:

seg000:3B3D loc_0_3B3D:                             ; CODE XREF: seg000:3B38 j
seg000:3B3D                 mov     sp, 100h
seg000:3B40                 mov     bx, word_0_21A
seg000:3B44                 mov     si, 1F6Bh
seg000:3B47                 mov     cx, 1FFFh
seg000:3B4A                 call    sub_0_40F2
seg000:3B4D                 mov     si, 3B56h
seg000:3B50                 mov     cx, 3D6Ah
seg000:3B53                 call    sub_0_40F2
seg000:3B53 ;-------------------------------------------------------------------
seg000:3B56                 db 0C0h ; +
seg000:3B57                 db  8Dh ; ì
seg000:3B58                 db  3Dh ; =
seg000:3B59                 db  4Ch ; L
seg000:3B5A                 db  12h ;
seg000:3B5B                 db  26h ; &
seg000:3B5C                 db  5Ch ; \

At 3B53, we have a call, followed by garbage instead of executable code. Yet the program doesn't flake out if you continue to execute. This is a strong hint of encrypted code. But how is it decrypted? If I was Zen-cracking, and admittedly I wasn't, I would studied the listing further before jumping immediately to fire up debug. But I rushed to execute the program using debug ("t" for single stepping, "g" address for executing until a certain address)  Executing the program until 3B53, and then to the end of the 40F2 subroutine (4104), and then executing the return brings me back to 3B56. But lo and behold, 3B56 is no longer garbage. Instead, we now have new, sensible code:

seg000:3B4D                 mov     si, 3B56h
seg000:3B50                 mov     cx, 3D6Ah
seg000:3B53                 call    sub_0_40F2
seg000:3B56                 mov     word_0_21A, bx
seg000:3B5A                 mov     ds, ds:2Ch
seg000:3B5E                 mov     si, 0

Now, we know sub_0_40F2 is the decryption routine. and look what we have, yet another decryption call, this time with different si and cx values. After executing sub_0_40F2, we have new executable instructions at 3B56. Looking back on our most recent encryption call, we see that the values of si and cx were 3B56 and 3D6A, the beginning and end of the area that was decrypted. From this we deduce that the next call will decrypt the region 3B56-3D6A, and sure enough, it does.

By studying the encryption code, you will see that bx is the decryption key.  After each decryption, bx is saved to be used as the decryption key for the next decryption.

We can save the decrypted output with debug, using the "w" command. But, we must set the ip at 100 (the start of .COM files), cx at 5300 (the length of the file), and the remaining regular registers to 0.

Now, look through the decrypted code for the shareware expiration message. What, it isn't there?! Why? The author of the program has encrypted his program section by section. As you have seen, we already executed several decryptions, the first at 3B4A, and the second at one at 3B53.

We now enter a cycle of

  1. execute under debug to decrypt,
  2. save the decrypted output
  3. disassemble the decrypted output ,
  4. look at the code for more decryption references (scan for sub_40F2 in the listing) and
  5. set breakpoints and repeat another round of decryption execution.

We could just single step our way through with debug instead of using dead-listings. But this would make us old before our time. Eventually, like tediously unwinding a ball of yarn, we have the program laid bare. There are a total of 8 encrypted regions in the program:

Input Key Encrypted Region Decrypt Call From New Key
4435     1F45-1F6B   40D9 38BF
38BF 1FFF-3B56 40E2 0AB6
0AB6 1F6B-1FFF 3B44 5FA4
5FA4 3B56-3D6A 3B4D 3B48
3B48 4105-4FE3 3C0A FD27
FD27 3D6A-4083 3C19 909B
909B 4FE3-5391 4FDA D10D
D10D 0638-1F45 4FE5 4E2F


Of course, in real life, I did not skip directly from one encrypted region to another. Several obstacles came up:

From the listing, we find that the "corrupted program" message at is found at 20D7. Searching for 20D7, we see that at 3B8F, the program does a mov dx, 20D7. However, there is not an immediately obvious DOS print routine. With a little more sleuthing, we find that the print routine is at 36F2. A little more sleuthing reveals that the checksum is called from 3B8C, which is executed before the print routine is called to display the "corrupted program" message.

Here is the checksum routine:

seg000:05F8 ;               S u b r o u t i n e
seg000:05F8 sub_0_5F8       proc near               ; CODE XREF: seg000:3B8C p
seg000:05F8                 sub     cx, si
seg000:05FA                 inc     cx
seg000:05FB                 shr     cx, 1
seg000:05FD                 xor     ax, ax
seg000:05FF loc_0_5FF:                              ; CODE XREF: sub_0_5F8+D j
seg000:05FF                 add     ax, [si]
seg000:0601                 add     ax, si
seg000:0603                 inc     si
seg000:0604                 inc     si
seg000:0605                 loop    loc_0_5FF
seg000:0607                 retn
seg000:0607 sub_0_5F8       endp

Look at this routine. It "feels" like a checksum, with the loop and the addition of the contents of [si].

Notice what happens when the checksum is called:

seg000:3B8C                 call    sub_0_5F8
seg000:3B8F                 mov     dx, 20D7h ; corrupt program message
seg000:3B92                 test    ax, ax
seg000:3B94                 jz      loc_0_3B99 ; ok to proceed
seg000:3B96                 jmp     loc_0_3679 ; beggar off, corrupt program

For now, let's put this question aside, and stick to our goal of removing the 90 day time limit. We have unmasked the decryption.


Now that the program has been laid out in unencrypted form, we can look for the 90 day crack. We look for the expiration string and find it at 4221. Searching for references to the string, we find nearby a cmp ax, 5AH (90 decimal) at 4F9B, which is where the expiration date checking is done.

seg000:4F81 ;-------------------------------------------------------------------
seg000:4F81 loc_0_4F81:                             ; CODE XREF: seg000:4F67 j
seg000:4F81                                         ; seg000:4F6F j
seg000:4F81                                         ; ...
seg000:4F81                 mov     ax, 5D06h
seg000:4F84                 int     21h             ; DOS - 3.1+ internal - GET
seg000:4F84                                         ; Return: CF set on error, C
seg000:4F86                 push    ds
seg000:4F87                 pop     es
seg000:4F88                 assume es:seg000
seg000:4F88                 pop     ds
seg000:4F89                 mov     word ptr loc_0_184, es
seg000:4F8D                 mov     word ptr loc_0_182, si
seg000:4F91                 mov     bx, si
seg000:4F93                 mov     ax, es:[bx+34h]
seg000:4F97                 sub     ax, word ptr loc_0_119+1
seg000:4F9B                 cmp     ax, 5Ah ; compare to 90 days
seg000:4F9E                 mov     dx, 4221h ; 90 days up message
seg000:4FA1                 jle     loc_0_4FA6 ; continue if not
seg000:4FA3                 jmp     loc_0_3679 ; print and abort if more

We crack this by replacing the cmp ax, 5ah with xor ax,ax, which always returns a zero for the jle.

We must also disarm the decryption routines, because they would scramble our carefully unscrambled code. We also have to disarm the checksum routines.

There are several avenues to disarm the decryption.. We could simply nop out the calls (this is a .COM file and there is no problem relocation patching done by the loader). However, this may not be the safest strategy. In disarming the decryption routines, we note that the output of one decryption is used as the input of another, and that upon exiting the decryption routine, the new password is stored in bx and in the variable dw 21A. To be safe, we can replace the calls with mov bx, yy, where yy is the decryption password. That way, we mimic the return value of the encryption. Any undiscovered calls to the decryption routines (perhaps by the TSR code, which we haven't analyzed in depth) would find the appropriate value in dw 21A to decrypt other parts of the code as necessary. It is possible, but not likely, that the TSR code hides some of this. A crack using the strategy just described appears to load and run correctly. But we haven't tried all the command line options.

To disarm the checksums, we can either find where the appropriate value is added in each checksummed region to make the checksum turn out 0, or we can look for the calls to 5F8 and replace them with

xor ax,ax
nop (3 bytes)

to make the following checks see the zero they want. The latter is the easier route which we take.

In my first attempt at removing the checksums, I removed all three checksum calls, which turned out to be a mistake.  Two of them were used to check the integrity of data files that are written to disk.  Only the one discussed above is the appropriate checksum to remove.

It is trival to remove the "This program is unregistered" message.

The registered version of the program has a command line option for suppressing the start-up messages. Although this functionality is not built into the program, we can add it. Our job is made easier because the program writes to the screen using DOS calls, not BIOS calls. Thus, it is easy, almost trivial, to changing the program's output from standard error (non-suppressable) to standard output (which can be redirected to >nul or to a file). Note not all changes are this easy. If the program writes using BIOS calls, we would have to ADD code to jump around screen writes, which is not a trivial task.

This can be done by the following change

seg000:3716                 mov     ah, 40h
seg000:3718                 mov     bx, 2	; HERE- change from 2 to 1
seg000:371B                 int     21h         ; DOS - 2+ - WRITE TO FILE
seg000:371B                                     ; BX = file handle, CX =num

There do not appear to be any more encrypted areas, or if there are, the storage of the password values into BX in the patch is enabling them to be decrypted correctly.

That should be it for this crack.

Final Notes

The TSR routines that the program uses to actually correct the PC clock time seem quite interesting in themselves. For those who are interested (maybe in porting these routines to Linux?), they would make a good full-scale reversing exercise.  This exercise is left for the reader.  The documentation in the rightime archive on clock correction would be the place to start.  Please share your discoveries with all of us.

Ob Duh

I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell.

You are deep inside fravia's page of reverse engineering, choose your way out:

redhomepage redlinks redsearch_forms red+ORC redstudents' essays redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_fravia+
redIs reverse engineering legal?