The cracking of XVision


[Home] Home > Blog >
The cracking of XVision

Solving my own problems.

1. The problem

Visionware's XVision is actually pretty nice. It has a pretty decent X server that runs on Windows 3, Windows 9x, and Windows NT. I even found it has a proper VT320 terminal emulator (I'll post more on that later). All in all, I enjoy using it, and I want to continue using it. I even bought version 5.10 from eBay.

However, the versions that are floating around on the Interwebs involve the same key from the days of altalavista.box.sk, and that key is a demonstration key.

What to do. XVision is still technically a current product owned and sold by MKS, but we're talking about a version of that product that, at its core, is a 16-bit application, with an X server that makes use of win32s to provide 32-bit functionality. This stuff won't work on anything that lacks a 16-bit Windows-on-Windows subsystem. You're either using the 32-bit version of Windows 10 or earlier, or you're not using XVision.

Add on top of that the fact that we're talking about a Visionware product. The company was acquired by SCO, had its licensing system changed with XVision Eclipse, then sold on to MKS. Is there still even a way to generate licenses for XVision 6, 5, and earlier? Probably not.

I've managed to convince myself that, ethically, this is ok. I feel I wouldn't be taking food out of the mouths of hungry developers in this instance. At least not with versions older than XVision Eclipse.

I already have a legit license for Eclipse, and it's pure 32-bit software, so I don't want to do anything with that. I don't need to.

So, with that out of the way. Let's crack on with this.

2. The license format

I found an article on scosales that documented the format of the XVision 6.x (and earlier) serial number format, which is how I found out that the license I was using was demonstration. This is interesting, as it points out that 'D' and 'S' are special. So there must be some sort of checking for this, right?

For XVision 6.01 and earlier (pre-XVision 7.0 Eclipse releases), XVision requires both a serial number and an activation key for installation. The serial numbers contain 15 digits (numbers), for example, "040006010054321". The "04" indicates that this is a serial number for XVision. The "601" towards the middle of the serial number indicates that this is XVision version 6.01. If there is a capital "D" inserted midway into the serial number, for example, "040006010D54321". The software is a "Demonstration" or "Evaluation" copy which will time out 30 days after installation. If there is a capital "S" inserted midway into the serial number, for example, "040006010S54321", then the license is for a Site license. (See below for more details on Site Licenses.) A serial number must have 15 digits or it will not install. Any letters in the serial number must be entered as upper case. XVision 6.01 and earlier needs both a serial number and activation key for installation.

Activation Keys contain 12 characters, a combination of numbers and upper case letters, for example, "2CD579DFEE5A". There are no "O"s in XVision Activation keys, only "Zeroes". An activation key must have 12 characters, and all letters must be upper case and entered correctly or it will not install. Activation keys and serial number are matched sets. An activation key will not work with the wrong serial number.

In addition to a serial number and activation key, during installation a Site License will also prompt for an accurate entry of the company name, complete with spelling, case and punctuation. For example, if the customer has determined that the Company's name entry for XVision Site License installation is "Computers-R-Us, Inc.", it must be entered exactly as such. Typing in "Computers R Us, Inc" will not work.

Well, I'd certainly hope so. And it turns out that it would be pretty obvious that there would be checking for these magic characters.

3. Reverse engineering

So, I copied some executables and DLLs over to a GNU/Linux machine and did some objdump and strings spelunking. I didn't really find much, other than reference to functions with names like nl_checkserial. So I decided to copy the same files to my Windows 95 VM and run them past the rec decompiler.

Then, after loading them up in an editor, I decided to look for both the decimal and hexadecimal literals for the character 'D'. This got me some interesting results.

if(L00461330( & A258, A35c) >= 0) {
  eax = A35c + 2;
  ecx = 4962860;
  if(*eax == *L004BBA2C) {
    if(*(eax + 2) == *L004BBA2E) {
      ax = *A35c;
      if(ax == *L004BBA28) {
        goto L00461551;
      }
      if(ax == *L004BBA24) {
        goto L00461551;
      }
      if(ax == *L004BBA20) {
        goto L00461551;
      }
      if(ax == *L004BBA1C) {
L00461551:
        if(*A35c == *L004BBA1C) {
          if(*(A35c + 9) == 68) {  /* <-- Comparison against 'D' */
            return(3);
          }
          esp = esp + 424;
          return(4);
        }
        if(*(A35c + 9) == 68) {    /* <-- Comparison against 'D' */
          esp = esp + 424;
          return(1);
        }
        esp = esp + 424;
        return(0);
      }
    }
  }
}

Ignoring the meat of that function and focusing in on the comparisons, the variable A35c seems to be coming from the call to L00461330. Given that earlier on, there's calls to GetPrivateProfileStringA, OpenFileA, lseek, lread et al, I figured that this could potentially be something that:

  • Reads XVision's main ini to get program location,
  • Use that to build a filename using a mix of sprintf and strcat,
  • Open that file with OpenFileA
  • Read its contents,
  • etc.

So, this looks like it could be interesting to spend some time looking at. What does that mysterious function L00461330 do? Are my assumptions correct? Am I just making things up as I go along?

L00461330(A4, A8)
  /* unknown */ void  A4;
  /* unknown */ void  A8;
{
  eax = 0;
  dl = 0;
  do {
    dl = dl ^ *(A4 + eax);
    eax = eax + 1;
  } while(eax <= 19);
  if(*(A4 + 20) != dl) {
    return(-3);
  }
  eax = 0;
  dl = 0;
  do {
    dl = dl ^ *(A4 + eax);
    eax = eax + 1;
  } while(eax <= 12);
  if(*(A4 + 13) != dl) {
    return(-2);
  }
  eax = 0;
  dl = 0;
  do {
    dl = dl ^ *(A4 + eax);
    eax = eax + 1;
  } while(eax <= 5);
  if(*(A4 + 6) != dl) {
    return(-1);
  }
  eax = 0;
  do {
    *(A4 + eax) = *(A4 + eax) ^ *(eax + "Visionware PC-Connect 1990");
    eax = eax + 1;
  } while(eax <= 19);
  *A8 = *(A4 + 3);
  *(A8 + 1) = *(A4 + 17);
  *(A8 + 2) = *(A4 + 14);
  *(A8 + 3) = *(A4 + 8);
  *(A8 + 4) = *A4;
  *(A8 + 5) = *(A4 + 15);
  *(A8 + 6) = *(A4 + 19);
  *(A8 + 7) = *(A4 + 11);
  *(A8 + 8) = *(A4 + 7);
  *(A8 + 9) = *(A4 + 1);
  *(A8 + 10) = *(A4 + 4);
  *(A8 + 11) = *(A4 + 12);
  *(A8 + 12) = *(A4 + 16);
  *(A8 + 13) = *(A4 + 9);
  *(A8 + 14) = *(A4 + 5);
  *(A8 + 15) = 0;
  return(0);
}

Woah woah woah woah, is that XOR encryption? Is it doing some byte swapping for added obfuscation? Yes, and yes. This is the exact code for taking serialised data from, say, c:\xvision\serial.no and reconstructing it back to the serial number entered during initial installation. This is probably the code that gets used when you go to About and it prints out your serial number so that you feel good about spending a few hundred bucks on your software. You did do that, right? :)

There is also a similar function that uses a longer key, presumably that would be used for site licenses. We're going to ignore that for now, though.

Ok, so let's take that code and write something in C that can take the binary data and reconstruct the original serial.

// Attempt to decode a serialised blob into a serial.
//
// Returns:
//   0  =  Successful decode.
//   1  =  Byte 6 XOR'd value is invalid.
//   2  =  Byte 13 XOR'd value is invalid.
//   3  =  Byte 20 XOR'd value is invalid.
int
serial_decode(char* serial, char* output)
{
  char*  str = NULL;
  int    idx = 0;
  uint16 dl  = 0;

  if ((str = (char *)malloc(sizeof(char) * 32)) == NULL) {
    MessageBox(NULL,
               "Could not allocate memory!",
               "Fatal Error",
               MB_OK | MB_ICONEXCLAMATION);
    exit(1);
  }
  memset(str, 0, 32);

  // Check the byte 20 XOR value.
  do {
    dl = dl ^ *(serial + idx);
    idx++;
  } while (idx <= 19);
  if (*(serial + 20) != (char)dl) {
    return 3;
  }

  // Check the byte 13 XOR value.
  idx = 0;
  dl  = 0;
  do {
    dl = dl ^ *(serial + idx);
    idx++;
  } while(idx <= 12);
  if ((char)*(serial + 13) != (char)dl) {
    return 2;
  }

  // Check the byte 6 XOR value.
  idx = 0;
  dl  = 0;
  do {
    dl = dl ^ *(serial + idx);
    idx++;
  } while(idx <= 5);
  if (*(serial + 6) != dl) {
    return 1;
  }

  // XOR the serial against the magic key.
  idx = 0;
  do {
    char tmp = (char)*(str + idx) ^ (char)*(s_magic + idx);
    *(str + idx) = tmp;
    idx++;
  } while (idx <= 19);

  // Swap bytes.
  *output        = *(str + 3);
  *(output + 1)  = *(str + 17);
  *(output + 2)  = *(str + 14);
  *(output + 3)  = *(str + 8);
  *(output + 4)  = *str;
  *(output + 5)  = *(str + 15);
  *(output + 6)  = *(str + 19);
  *(output + 7)  = *(str + 11);
  *(output + 8)  = *(str + 7);
  *(output + 9)  = *(str + 1);
  *(output + 10) = *(str + 4);
  *(output + 11) = *(str + 12);
  *(output + 12) = *(str + 16);
  *(output + 13) = *(str + 9);
  *(output + 14) = *(str + 5);
  *(output + 15) = 0;

  return 0;
}

It's pretty simple how it works. There are some added obfuscation steps, like comparing the values of bytes 6, 13, and 20 against the results of XOR'ing the preceding bytes, but all in all it's pretty simple. We can just reverse the polarity of the neutron flow and build the serialised blob from an arbitrary serial number, right?

// Generate a serizlised serial number from the source serial in STR.
void
serial_generate(const char* str, char* output)
{
  int    idx = 0;
  uint16 dl  = 0;
  char   tmp = 0;

  // Start by swapping bytes.
  output[3]  = *str;
  output[17] = *(str + 1);
  output[14] = *(str + 2);
  output[8]  = *(str + 3);
  output[0]  = *(str + 4);
  output[15] = *(str + 5);
  output[19] = *(str + 6);
  output[11] = *(str + 7);
  output[7]  = *(str + 8);
  output[1]  = *(str + 9);
  output[4]  = *(str + 10);
  output[12] = *(str + 11);
  output[16] = *(str + 12);
  output[9]  = *(str + 13);
  output[5]  = *(str + 14);

  // XOR with the magic key.
  idx = 0;
  do {
    tmp = (char)*(output + idx) ^ (char)*(s_magic + idx);
    *(output + idx) = tmp;
    idx++;
  } while (idx <= 19);


  // Generate XOR value for byte 6.
  idx = 0;
  dl  = 0;
  do {
    dl = dl ^ output[idx];
    idx++;
  } while(idx <= 5);
  output[6] = dl;

  // Generate XOR value for byte 13.
  idx = 0;
  dl  = 0;
  do {
    dl = dl ^ output[idx];
    idx++;
  } while(idx <= 12);
  output[13] = dl;

  // Generate XOR value for byte 20.
  idx = 0;
  dl  = 0;
  do {
    dl = dl ^ output[idx];
    idx++;
  } while (idx <= 19);
  output[20] = dl;
}

Yes, of course we can. This is simply the same steps as the decoder, just in reverse. Ensuring that we spend time to insert the correct values at bytes 6, 13, and 20. This doesn't result in exact same values when converting from serialised data to serial number to serialised data, but the canary bytes work, so the that should be enough for XVision, right?

4. Testing some presumptuous presumptions

After a little while bashing out some C, I decided to add some fwrite goodness and generate my own serial.no.

Testing!

Alright, so seems it can take the serialised blob, convert it into a serial, then back into a blob. Will this generate a valid serial.no? let's try.

Generating a serial.no file

Aw gee, Rick, I hope this works, Rick, because this sounds too easy. Aw gee, Rick, what if it doesn't work?

Yup, works.

Yup.

Now XVision is fully-functional, with no nags. Outstanding.

Mischief managed.

5. The future

The code that takes the serial number and does magic with the key is buried deep in what looks like a 16-bit virtual machine. It's pretty hairy, as is debugging any virtual machine, so I didn't quite feel up to spending a few hours doing that just yet. I would like to revisit it, though.

I'd also like to look closer at how to generate a site license at some point, too. Just out of curiosity. I think I've done enough to keep this software functional, and that was the overall aim.

6. But wait, there's more.

I decided to fire up Borland C++ and write a tool that would do all this for me, just for kicks. I've not used ObjectWindows for a long, long time.

Go go Borland C++

This is what the result looks like. It's a 16-bit app, so it will work on Windows 3 as well as NT 3, NT4, 9x and so on.

Aaaand it works.

Any version of Windows that is newer than those and you honestly want XVision Eclipse.

I will not be cracking XVision Eclipse, so please do not ask.

If you wish to play with this with your own XVision installation, then the binary (and sources) are available right here.

If you want to play with the source code, you'll want Borland C++ 5 with ObjectWindows and BWCC.

7. Some interesting notes

Firstly, the 04000 prefix is meaningless. I wonder if they're reusing another licensing schema where the 04 is a product id and the other bytes are number of users.

The product version is also meaningless. The demo license I used was for XVision 5.10, not 5.0 or 5.6. It also worked on 6.01.

It would be interesting to see if this stuff works on other Visionware products, such as PC-Connect. Chances are it will.

Maybe one day I'll locate a copy of PC-Connect.

Return to top


Made with weblorg
Copyright © 2010-2024 Paul Ward.