PC Floppy Copy Protection: EliaShim CodeSafe


This is part 6 of a series of articles investigating various floppy copy-protection schemes seen on the IBM PC platform. You may wish to read the previous entries in this series:


EliaShim Microcomputers was a computer technology firm based in Haifa, Israel, with a US-based office in Tampa, Florida.

From 1983 to the early 1990's they marketed a number of copy-protection technologies under the CodeSafe trademark

By the 90's these mostly included various dongles, with the age of floppy-based protections drawing to a close.  


EliaShim, unlike many of the companies we've covered, would more or less successfully pivot to different market in the mid 90's. EliaShim ViruSafe, an anti-virus product for DOS, Windows 3.1, 95 and NT, saw moderate success, enough to get EliaShim acquired by Aladdin Knowledge Systems for $23 million in 1998.  Eventually renamed as eSafe, this technology lived on in various forms through mergers and acquisitions, eventually ending up with SafeNet, makers of the famous Sentinel brand of security dongles.  SafeNet themselves were acquired by Thales in 2019, who still sells Sentinel dongles under their Software Monetization product line.

Thanks to the wonders of the Internet Archive's Wayback Machine, you can experience EliaShim's web page circa 1996 here.   You can see that by 1996, there is already no mention of their previous copy-protection product line.

CodeSafe




CodeSafe seemed to be marketed in a similar way as Vault's Prolok product - in that EliaShim would be happy to sell you specially made "protection diskettes" upon which to put your software product, however these didn't appear to be physically altered. At least, there aren't any laser holes in them.

CodeSafe was not a particularly common copy protection scheme in the United States, and frankly I was only made aware of it when discussion of a CodeSafe-protected title popped on the VOGONS forums.

What CodeSafe does isn't all that dramatic, but it is a slight variant on the methods we have seen thus far, so worth a quick look.

Ktavkat

The program we'll be examining is called Ktavkat, or כתבקט in Hebrew. It is a word processor for kids made by Bar-Code Computers, Ltd., a company that is still in business making medical imaging software.

The Internet Archive has a Kryoflux image of this software, which we will convert to PRI format for MartyPC to run using Hampa Hug's PFI.EXE utility thusly:

pfi "T:\Disk Images\ktavkat\track00.0.raw" -s weak-bits 1 -p decode pri "T:\Disk Images\ktavkat.pri"

Let's take a look at the disk visualization, first. The data layer is a bit distracting on this one, so I will isolate just the metadata regions:

Ktavkat (1991) Byte-Code Computers 



Both sides of the  disk have a protection track, Track 7. As usual, the protection tracks are full of unusually sized and overlapped sectors, however the protection manages to maintain a valid data CRC for each sector. 

Disks, Lies and Magnetic Media

In my article on Formaster Copy-Lock, I discussed the various fields that a copy-protection scheme can lie about.  Here's a refresher:

Sector ID Address Mark and header fields

In the header of each sector are four byte values:
  • c, the cylinder/track number
  • h, the head number
  • s, the sector ID
  • n, from which the size of the sector can be calculated by shifting 128 left 'n' times.
A copy protection scheme can lie about any of these values.  That is to say, a sector on cylinder 0 may say it is on cylinder 20 - a sector on Side 0 of a disk may say it is written by head 1.  It may be the first sector on a track, but have a sector ID of 222. It may occupy 512 bytes on a track, but say it is only 256 bytes long.

Several sectors on CodeSafe's protection tracks lie about their cylinder and head values.

If we dump the track layout from both sides:

Head 0, Track 7
    [c:80 h:0 s:5  n: 5]
    [c:80 h:0 s:5  n: 3]
    [c:80 h:0 s:6  n: 3]
    [c:80 h:0 s:7  n: 3]
    [c:80 h:0 s:8  n: 1]
    [c:7  h:0 s:5  n: 2]
    [c:0  h:0 s:9  n: 1]
    [c:0  h:0 s:11 n: 1]
    [c:0  h:0 s:12 n: 1]
    [c:7  h:0 s:6  n: 2]
    
Head 1, Track 7
    [c:80 h:0 s:5  n: 5]
    [c:80 h:0 s:5  n: 3]
    [c:80 h:0 s:6  n: 3]
    [c:80 h:0 s:7  n: 3]
    [c:80 h:0 s:8  n: 1]
    [c:7  h:0 s:5  n: 2]
    [c:0  h:0 s:9  n: 1]
    [c:0  h:0 s:11 n: 1]
    [c:0  h:0 s:12 n: 1]
    [c:7  h:0 s:6  n: 2]

We can see that the sectors on Side 1 - using Head 1, all still reference Head 0.  Likewise, they're all over the place with their cylinder references, only two of them actually having the correct cylinder number in their sector headers.

We run this program by typing 'k' at the DOS prompt. If we fail the copy-protection check - and we do - CodeSafe is kind enough to identify itself and provide an error message before dropping us off at the DOS prompt: 


After all the stuff we've fixed so far in this series, what could possibly be causing MartyPC issues now?

To understand, we need to look a bit at how commands to a PC floppy controller are constructed. To send a Read Data command to get some data off a floppy disk, you must send a sequence of 9 bytes:

The Read Data command's two head specifiers

Byte 1 identifies the command itself in the lower five bits, along with some miscellaneous flags. 
Byte 2 identifies the drive number - from 0-3, in the first two bits (this allows for up to 4 floppy disks per controller). The third bit, identifies the head - either 0 for head/side 0, or 1 for head/side 1.

Then we have Byte 4 - which again, specifies the head number.

This seems a bit odd at first glance - we have two fields in which the head is specified, and in theory, they could be different from each other.  What would happen if they are?

In practice, it is the HD bit in Byte 2 that determines which physical head is used to read the sector.  Bytes 3, 4, 5, and 6 are used to match the values c, h, s, and n in the sector header.  You can lie all you want about the head value stored in the sector header - but you have to give the controller the correct head to use in Byte 2.

Note the IPS bit in Byte 2.  This is the Implied Seek bit - this is a later addition to the floppy controller protocol.  When Implied Seek is set, if the track number specified in Byte 3 differs from the current track, the drive will first seek to that track before executing the command.   Handy - but we're not using that bit yet.  With no implied seek, it is possible to read sector headers with any value of 'c' from the current track.

Here's the declaration of the read_sector function in my disk library, fluxfox currently:

    pub fn read_sector(
        &mut self,
        chs: DiskChs,
        n: Option<u8>,
        scope: RwSectorScope,
        debug: bool,
    ) -> Result<ReadSectorResult, DiskImageError> {

Spot the problem?

I'm using a structure, DiskChs, that holds the values c, h, and s to identify the sector to read. This effectively works like an implied seek - but this won't work if c or h are lies!

We have to specify the physical cylinder and head value, and the values of c, h and s to match in the sector header separately.

Our revised function declaration will look like this:

    pub fn read_sector(
        &mut self,
        phys_ch: DiskCh,
        id_chs: DiskChs,
        n: Option<u8>,
        scope: RwSectorScope,
        debug: bool,
    ) -> Result<ReadSectorResult, DiskImageError> {

Now we provide the physical cylinder and head, as well as the c, h, s and n values that should be matched in the sector header. 

When calling this function, the physical cylinder will just be the last cylinder we seeked to - as we are not implementing implied seeks.  The physical head will be the HD bit in Byte 2 of the command sequence.

We need to make a similar fix to the Read Track command, which will read the track the drive head is currently seeked to, not the track number provided in the command phase (without implicit seek enabled).

There is also a 'wrong cylinder' (WCYL) bit we should set in the ST2 status byte, if the controller fails to find a sector ID, but notices that the cylinder fields in the sector headers for the track do not match the request.

Partial Sector Reads

Here's another wrinkle we're not handling - typically two things can stop a Read Data operation when in DMA mode.  Either the DMA terminal count (indicating the transfer has completed) or reaching the End of Track (EOT) sector ID value specified in the command. 

The way MartyPC currently calculates the number of sectors to read is to determine how many bytes the DMA controller has been programmed for, and to divide that by the sector size, calculated from n.  But is that a safe assumption?

As it turns out, it is possible to read only part of a sector, just by programming the DMA controller for less than the sector size. Say you had a 4096 byte sector, but programmed the DMA transfer for 512 bytes - you could still read those first 512 bytes.

This just requires a little adjustment in code to increment the sector count by one if the modulus of the DMA word size and the expected sector size is not 0.

Success

With our fixed-up Read Data and Read Track commands, we can finally pass the protection check:

Ktavkat in MartyPC 0.3.0

A Quick Peek at the Code

Thankfully we don't have to debug the protection code directly for this one, but I thought I'd take a quick look at it to see if there was anything particularly interesting going on. The main executable for Ktavkat is K.EXE.

K.EXE, par for the course, is heavily obfuscated. It starts out with a short routine that immediately XOR-decodes a block of memory, which it ends up running into after the decoding loop instruction completes, without taking any jumps.

From there it begins a bunch of self-modifying code, with frequent rep movsw instructions overwriting the immediate instruction flow. A routine at offset 0137 is repeatedly overwritten, which makes trying to capture a disassembly listing from the running program fruitless.

As usual, the single-step interrupt vector 1h is overwritten, and the new vector installed there is called by the protection code. We saw Prolok do a similar thing - if you manage to reclaim the interrupt vector and step the code in your debugger, you won't run possibly critical sections of the protection code as a consequence.

A few decoded layers deep, and inside of a single-step interrupt handler, we arrive at fairly normal looking disk code.

The program avoids calling interrupt 13h for disk access during its protection check, so it can't simply be hooked like Prolok could be.  This means direct IO access to the floppy disk controller.  

A cracker might suspect this, and start scanning the running code segment, for say, the FDC's Data port address, 0x3F4.  He won't find it. 

How do you get 0x3F4 into the DX register without it ever appearing in memory?

Easy:

2F48:0CE0   mov     dx, 118Ch 
2F48:0CE3   xог     dx. 1278h
2F48:0CE7   in      al, dx

0x118C ^ 0x1278 == 0x3F4

So the port address can never be directly found, except when it is calculated inside a register. Clever!

The code performs similar XOR-based obfuscation of the command bytes sent to the floppy controller. 

If the protection code goes sideways, it doesn't immediately fail, but it causes the wrong disk operation to occur in the next sequence.  You may see odd or unsupported commands issued, such as Write Deleted Data.  This is a red herring - you've already failed the protection check at that point.

According to crazyc from the MAME project, who analyzed the code here a bit further than I did, the protection reads across the index, similar to what we saw with Superlok v3.  This implies that if using a bitstream image format, a format that encodes absolute bit length for tracks is required for this protection.









Comments

  1. This is a really interesting series -- thank you!

    BTW, there is one typo in this article:
    0x118C ^ 0x1278 == 0x3DF

    You intended to write 0x3F4, which would be the correct result.

    ReplyDelete

Post a Comment

Popular posts from this blog

PC Floppy Copy Protection: Softguard Superlok

PC Floppy Copy Protection: Formaster Copy-Lock

The Complete Bus Logic of the Intel 8088