Author Topic: Reversing The Encoding Of Serial Numbers In PALs or GALs  (Read 34962 times)

0 Members and 2 Guests are viewing this topic.

Offline OzOnE

  • Regular Contributor
  • *
  • Posts: 55
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #25 on: December 02, 2016, 01:15:51 pm »
Looks like the Hex version of the Verilog matches with what we're expecting from the EQN. :)

It should do really, as it's just representing the addresses in Hex rather than binary / separate bits.

http://pastebin.com/UDAPA52v

OK, so, erm. lol

I'll have a look at some other serial JEDs now, and try to figure out what that 68K routine is doing.

Good progress though, as we could eventually implement this as either a simple ROM, or a PIC / AVR / CPLD / FPGA which can do any serial number.
Although, that's obviously overkill if a simple PROM would do the job.

This will be great for MAME use too - I'll try to get more of that working, but it will take a while.
I never got the original Paintbox to fully boot on MAME, as although I had the full service manual with schematics, there were a fair few things I needed to test with the real machine in front of me.

OzOnE.

 

Offline OzOnE

  • Regular Contributor
  • *
  • Posts: 55
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #26 on: December 02, 2016, 01:33:39 pm »
OK, here's the main routine which seems to hash the PAL with that Quantel string...

http://pastebin.com/dpjXhTL1

EDIT: The "n" from the Quantel string is missing off the end, as IDA didn't seem to let me set it as an ASCII string properly?
I'm fairly sure the string does just end with that "n" character though, as the 0x4E71 that follows it equates to a "NOP", and the 0x4E56 is an "RTS".


Most of the comments in there are from another guy who has been helping to reverse this.

If anyone could turn that routine into some C or even pseudo-code, that would be a huge help.

As far as we can tell, it does look like the security PAL is simply mapped directly to 0xFD0000 in the 68K address space.

I guess that when reading it using Word-addressing, the bytes would simply be output in the LSB of each access, with the upper bits floating (or tied High or Low)?


« Last Edit: December 02, 2016, 01:37:20 pm by OzOnE »
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #27 on: December 02, 2016, 10:28:47 pm »
OK, here's the main routine which seems to hash the PAL with that Quantel string...

http://pastebin.com/dpjXhTL1

Now I'm tempted to load that into a 68k emulator and see what it does...

What specific 68k chip is on the main CPU board? I heard Dexter say "I've got 68040 CPU boards but they're for a later machine" in his videos, so I assume 68030 or earlier?


EDIT: The "n" from the Quantel string is missing off the end, as IDA didn't seem to let me set it as an ASCII string properly?
I'm fairly sure the string does just end with that "n" character though, as the 0x4E71 that follows it equates to a "NOP", and the 0x4E56 is an "RTS".

There's two ways I can think to do that in IDA. One is to change the length of the string array, the other is to mark the "nNqNV" as byte data. That'll split it into five bytes. Mark the latter four as code, then mark the three preceding as data.


Is there any chance I could get a copy of the ROM which was disassembled to produce that code snipped, or the IDA file?  (PM if necessary)

Have you got some examples of input to that function? Ideally input and output when it's working?
At cursory glance (two minutes) that looks more like it decrypts a licence string based on the PAL, not figures out the PAL's serial number.


If I had a second machine and I had PAL source and licence keys, I'd just do a Three Musketeers attack on the thing. Clone a licence PAL from one machine to another, then copy the licence keys across.


As far as we can tell, it does look like the security PAL is simply mapped directly to 0xFD0000 in the 68K address space.

I guess that when reading it using Word-addressing, the bytes would simply be output in the LSB of each access, with the upper bits floating (or tied High or Low)?

Excellent! Should be enough there to bash together an emulator. Just need a ROM image and/or IDA file.

Cheers.
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #28 on: December 02, 2016, 11:20:08 pm »
Got bored, printed the code off instead, got a pencil. As you do.


LOC_52EC..LOC_52F4 => this is a loop which clears *addr0 to $FFFF, word-by-word.

52F8: Get the address of the input "crypto string"
52FC: mustContain7777777[7] = '\0'. In other words, null terminate it at the 8th character. Leaving you with 7 characters/bytes... assuming it's a C string, and it's not filling it in with something else later.
   I wonder if this is the serial number.

5300: Chuck the address of the PAL into A1
5306: D0 = $1FFF. 8191. Half the number of bytes in the PAL. The PAL is accessed in words and the address (A1) is auto-incremented. So this fits with 13 address bits.
    Clear D1(word) and D7(dword) on entry

Now there's a loop which reads the PAL. The loop counter is D0

At 5320, the code accesses addr0 -- it reads it, then writes back to it. What I'm not certain of from this code snippet is whether addr0 is a temporary buffer (i.e. the "data I read from the PAL" buffer), or some kind of encoding table used to decode the data in the PROM.


So from this we can kind of deduce that the function works a little like this:

int readSecurityPAL_ChecksumCrypto(uint8_t *inputCryptoString_len3F, uint16_t *addr0, void *addr1_mustContain7777777)
   inputCryptoString_len3F:
     Crypto string.
     On entry contains the encrypted crypto string
     On exit contains the decrypted (EOR'd) crypto string
   addr0:
     Accessed in 16-bit words.
     Seems to be a decoding table for the PAL (used to decode the addresses)
   addr1_mustContain7777777:
     8-byte byte array
     Checked after the PROM has been read.
     If this is not equal to {7,7,7,7,7,7,7,0}, then an error has occurred. (PROM not present?)
     Serial number?


I'm still looking at what the inner loop does. It looks like the guys elsethread are on the right sort of track though. It scans up through the PAL addresses, then it processes  a few bits which come out of the PAL.

It's a bit of a headscratcher without seeing the rest of the code around it (or at least "seeing it running"). But hey ho.
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 14786
  • Country: gb
    • Mike's Electric Stuff
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #29 on: December 03, 2016, 12:23:54 am »
Quote
...leaving you with 7 characters/bytes

7 product terms in the PAL - coincidence..?
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #30 on: December 03, 2016, 01:24:19 am »
Quote
...leaving you with 7 characters/bytes

7 product terms in the PAL - coincidence..?

Probably not...!

I've been spending a few hours poking at the GALs. I've decoded all the GALs with JED2EQN, then converted the EQN syntax into C. My plan is to loop through and make a "security PAL emulator" I can bolt onto a 68K core. Watch this space.

Curiously every single PAL has its outputs inverted. So normally, the data outputs will be high, unless you hit an address which interests it.
Also, the data bits will probably read high if the outputs are disabled -- most systems I recall seeing have a pull-up resistor pack somewhere on the data bus.

As Mike quite rightly says -- there are seven product terms, which probably corresponds to those seven bytes in the "Addr1" response.

So based on the thinking that we have seven product terms, seven bytes in the "Addr1" return -- my bet is that "7777777" is a special return value which means "I can't find a security GAL, or it's broken".
Anything else means the GAL responded to something.

Looking at it another way, if we assume that's the maximum value, '7' is all bits set for a 3-bit value. A seven-bit array of three-bit values would get you 21 total bits encoded.
Potentially, eight outputs times seven Pterms on the GAL gets you 56 bits.
That's an encoding efficiency of 0.375... assuming these assumptions are right...
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #31 on: December 03, 2016, 01:48:38 am »
Some food for thought. This is a list of all the addresses which provoke a response from the "12360" GAL:

Code: [Select]
Addr 0x00AA -- value 0x7F -- inv 0x80
Addr 0x00F4 -- value 0xFB -- inv 0x04
Addr 0x0154 -- value 0x7F -- inv 0x80
Addr 0x0156 -- value 0x7F -- inv 0x80
Addr 0x0222 -- value 0xF7 -- inv 0x08
Addr 0x0228 -- value 0x7F -- inv 0x80
Addr 0x02C0 -- value 0xEF -- inv 0x10
Addr 0x0304 -- value 0x7F -- inv 0x80
Addr 0x03D2 -- value 0x7F -- inv 0x80
Addr 0x040A -- value 0xBF -- inv 0x40
Addr 0x0478 -- value 0xFE -- inv 0x01
Addr 0x04B0 -- value 0x7F -- inv 0x80
Addr 0x04C2 -- value 0xFD -- inv 0x02
Addr 0x055A -- value 0xEF -- inv 0x10
Addr 0x058C -- value 0xEF -- inv 0x10
Addr 0x06AE -- value 0xF7 -- inv 0x08
Addr 0x06E2 -- value 0xF7 -- inv 0x08
Addr 0x0734 -- value 0xF7 -- inv 0x08
Addr 0x0776 -- value 0xF7 -- inv 0x08
Addr 0x079E -- value 0xFB -- inv 0x04
Addr 0x08E4 -- value 0xBF -- inv 0x40
Addr 0x08FC -- value 0xFE -- inv 0x01
Addr 0x0960 -- value 0xFD -- inv 0x02
Addr 0x09C2 -- value 0xFB -- inv 0x04
Addr 0x0A7C -- value 0xEF -- inv 0x10
Addr 0x0ACA -- value 0xDF -- inv 0x20
Addr 0x0B62 -- value 0xFD -- inv 0x02
Addr 0x0B86 -- value 0xFE -- inv 0x01
Addr 0x0C4A -- value 0xF5 -- inv 0x0A
Addr 0x0C8E -- value 0xFB -- inv 0x04
Addr 0x0D02 -- value 0xBF -- inv 0x40
Addr 0x0D1E -- value 0xF7 -- inv 0x08
Addr 0x0D78 -- value 0xFB -- inv 0x04
Addr 0x0DEC -- value 0xBF -- inv 0x40
Addr 0x0E60 -- value 0xFB -- inv 0x04
Addr 0x0ECC -- value 0xDF -- inv 0x20
Addr 0x0F5E -- value 0xEF -- inv 0x10
Addr 0x103E -- value 0xEF -- inv 0x10
Addr 0x10F8 -- value 0xDF -- inv 0x20
Addr 0x1136 -- value 0xFD -- inv 0x02
Addr 0x121E -- value 0xFD -- inv 0x02
Addr 0x1232 -- value 0xFE -- inv 0x01
Addr 0x12D2 -- value 0xBF -- inv 0x40
Addr 0x1312 -- value 0xFE -- inv 0x01
Addr 0x1326 -- value 0xFB -- inv 0x04
Addr 0x1508 -- value 0xEF -- inv 0x10
Addr 0x15CC -- value 0xDF -- inv 0x20
Addr 0x1704 -- value 0xFD -- inv 0x02
Addr 0x1714 -- value 0xBF -- inv 0x40
Addr 0x17E6 -- value 0xFE -- inv 0x01
Addr 0x1A98 -- value 0xDF -- inv 0x20
Addr 0x1BE0 -- value 0xBF -- inv 0x40
Addr 0x1CC6 -- value 0xFE -- inv 0x01
Addr 0x1F76 -- value 0xDF -- inv 0x20
Addr 0x2040 -- value 0xDF -- inv 0x20


Number of addresses which returned non-FF bytes: 55


The C program which generated it:
Code: [Select]
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

int main(void)
{
bool A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13;
bool nD0,nD1,nD2,nD3,nD4,nD5,nD6,nD7;
FILE *fp;
unsigned int nNonFFs = 0;

fp = fopen("quansec.bin", "wb");

for (uint32_t addr = 0; addr < 0x4000; addr+=2)
{
uint8_t ch;

A1 = (addr & 2);
A2 = !(addr & 4); // Every single GAL we have samples of seems to have A2 set as inverted in hardware
A3 = (addr & 8);
A4 = (addr & 16);
A5 = (addr & 32);
A6 = (addr & 64);
A7 = (addr & 128);
A8 = (addr & 256);
A9 = (addr & 512);
A10 = (addr & 1024);
A11 = (addr & 2048);
A12 = (addr & 4096);
A13 = (addr & 8192);

#include "12360.c"


ch =
(!nD7 ? 128 : 0) |
(!nD6 ? 64  : 0) |
(!nD5 ? 32  : 0) |
(!nD4 ? 16  : 0) |
(!nD3 ? 8   : 0) |
(!nD2 ? 4   : 0) |
(!nD1 ? 2   : 0) |
(!nD0 ? 1   : 0);

if (ch != 0xFF) {
printf("Addr 0x%04X -- value 0x%02X -- inv 0x%02X\n", addr, ch, ch ^ 0xFF);
nNonFFs++;
}

fwrite(&ch, 1, 1, fp);
}
fclose(fp);
printf("\n\nNumber of addresses which returned non-FF bytes: %u\n", nNonFFs);
}


And my cleaned-up equations file:
Code: [Select]
/*
; JED2EQN -- JEDEC file to Boolean Equations disassembler (Version V063)
; Copyright (c) National Semiconductor Corporation 1990-1993
; Disassembled from 12360.JED. Date: 12-2-116
;$GALMODE MEDIUM

chip 12360 GAL20V8

A13=1 A12=2 A11=3 A10=4 A9=5 A8=6 A7=7 A6=8 A5=9 A4=10 A3=11 GND=12
!A2=13 A1=14 D7=15 D6=16 D5=17 D4=18 D3=19 D2=20 D1=21
D0=22 ALEC=23 VCC=24

@ues 0000000000000000

equations
*/

bool nD0 = (!A12 && !A13 && !A11 && A10 && !A9 && !A8 && !A7 && A6 && A5 && A4 && !A1 && A3 && A2)
    || (!A12 && !A13 && A11 && !A10 && !A9 && !A8 && A7 && A6 && A5 && A4 && !A1 && A3 && !A2)
    || (!A12 && !A13 && A11 && !A10 && A9 && A8 && A7 && !A6 && !A5 && !A4 && A1 && !A3 && !A2)
    || (A12 && !A13 && !A11 && !A10 && A9 && !A8 && !A7 && !A6 && A5 && A4 && A1 && !A3 && A2)
    || (A12 && !A13 && !A11 && !A10 && A9 && A8 && !A7 && !A6 && !A5 && A4 && A1 && !A3 && A2)
    || (A12 && !A13 && !A11 && A10 && A9 && A8 && A7 && A6 && A5 && !A4 && A1 && !A3 && !A2)
    || (A12 && !A13 && A11 && A10 && !A9 && !A8 && A7 && A6 && !A5 && !A4 && A1 && !A3 && !A2);
//D0.oe = !ALEC

bool nD1 = (!A12 && !A13 && !A11 && A10 && !A9 && !A8 && A7 && A6 && !A5 && !A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && A11 && !A10 && !A9 && A8 && !A7 && A6 && A5 && !A4 && !A1 && !A3 && A2)
    || (!A12 && !A13 && A11 && !A10 && A9 && A8 && !A7 && A6 && A5 && !A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && !A8 && !A7 && A6 && !A5 && !A4 && A1 && A3 && A2)
    || (A12 && !A13 && !A11 && !A10 && !A9 && A8 && !A7 && !A6 && A5 && A4 && A1 && !A3 && !A2)
    || (A12 && !A13 && !A11 && !A10 && A9 && !A8 && !A7 && !A6 && !A5 && A4 && A1 && A3 && !A2)
    || (A12 && !A13 && !A11 && A10 && A9 && A8 && !A7 && !A6 && !A5 && !A4 && !A1 && !A3 && !A2);
//D1.oe = !ALEC

bool nD2 = (!A12 && !A13 && !A11 && !A10 && !A9 && !A8 && A7 && A6 && A5 && A4 && !A1 && !A3 && !A2)
    || (!A12 && !A13 && !A11 && A10 && A9 && A8 && A7 && !A6 && !A5 && A4 && A1 && A3 && !A2)
    || (!A12 && !A13 && A11 && !A10 && !A9 && A8 && A7 && A6 && !A5 && !A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && !A8 && A7 && !A6 && !A5 && !A4 && A1 && A3 && !A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && A8 && !A7 && A6 && A5 && A4 && !A1 && A3 && A2)
    || (!A12 && !A13 && A11 && A10 && A9 && !A8 && !A7 && A6 && A5 && !A4 && !A1 && !A3 && A2)
    || (A12 && !A13 && !A11 && !A10 && A9 && A8 && !A7 && !A6 && A5 && !A4 && A1 && !A3 && !A2);
//D2.oe = !ALEC

bool nD3 = (!A12 && !A13 && !A11 && !A10 && A9 && !A8 && !A7 && !A6 && A5 && !A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && !A11 && A10 && A9 && !A8 && A7 && !A6 && A5 && !A4 && A1 && A3 && !A2)
    || (!A12 && !A13 && !A11 && A10 && A9 && !A8 && A7 && A6 && A5 && !A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && !A11 && A10 && A9 && A8 && !A7 && !A6 && A5 && A4 && !A1 && !A3 && !A2)
    || (!A12 && !A13 && !A11 && A10 && A9 && A8 && !A7 && A6 && A5 && A4 && A1 && !A3 && !A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && !A8 && !A7 && A6 && !A5 && !A4 && A1 && A3 && A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && A8 && !A7 && !A6 && !A5 && A4 && A1 && A3 && !A2);
//D3.oe = !ALEC

bool nD4 = (!A12 && !A13 && !A11 && !A10 && A9 && !A8 && A7 && A6 && !A5 && !A4 && !A1 && !A3 && A2)
    || (!A12 && !A13 && !A11 && A10 && !A9 && A8 && !A7 && A6 && !A5 && A4 && A1 && A3 && A2)
    || (!A12 && !A13 && !A11 && A10 && !A9 && A8 && A7 && !A6 && !A5 && !A4 && !A1 && A3 && !A2)
    || (!A12 && !A13 && A11 && !A10 && A9 && !A8 && !A7 && A6 && A5 && A4 && !A1 && A3 && !A2)
    || (!A12 && !A13 && A11 && A10 && A9 && A8 && !A7 && A6 && !A5 && A4 && A1 && A3 && !A2)
    || (A12 && !A13 && !A11 && !A10 && !A9 && !A8 && !A7 && !A6 && A5 && A4 && A1 && A3 && !A2)
    || (A12 && !A13 && !A11 && A10 && !A9 && A8 && !A7 && !A6 && !A5 && !A4 && !A1 && A3 && A2);
//D4.oe = !ALEC;

bool nD5 = (!A12 && !A13 && A11 && !A10 && A9 && !A8 && A7 && A6 && !A5 && !A4 && A1 && A3 && A2)
    || (!A12 && !A13 && A11 && A10 && A9 && !A8 && A7 && A6 && !A5 && !A4 && !A1 && A3 && !A2)
    || (A12 && !A13 && !A11 && !A10 && !A9 && !A8 && A7 && A6 && A5 && A4 && !A1 && A3 && A2)
    || (A12 && !A13 && !A11 && A10 && !A9 && A8 && A7 && A6 && !A5 && !A4 && !A1 && A3 && !A2)
    || (A12 && !A13 && A11 && !A10 && A9 && !A8 && A7 && !A6 && !A5 && A4 && !A1 && A3 && A2)
    || (A12 && !A13 && A11 && A10 && A9 && A8 && !A7 && A6 && A5 && A4 && A1 && !A3 && !A2)
    || (!A12 && A13 && !A11 && !A10 && !A9 && !A8 && !A7 && A6 && !A5 && !A4 && !A1 && !A3 && A2);
//D5.oe = !ALEC

bool nD6 = (!A12 && !A13 && !A11 && A10 && !A9 && !A8 && !A7 && !A6 && !A5 && !A4 && A1 && A3 && A2)
    || (!A12 && !A13 && A11 && !A10 && !A9 && !A8 && A7 && A6 && A5 && !A4 && !A1 && !A3 && !A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && A8 && !A7 && !A6 && !A5 && !A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && A11 && A10 && !A9 && A8 && A7 && A6 && A5 && !A4 && !A1 && A3 && !A2)
    || (A12 && !A13 && !A11 && !A10 && A9 && !A8 && A7 && A6 && !A5 && A4 && A1 && !A3 && A2)
    || (A12 && !A13 && !A11 && A10 && A9 && A8 && !A7 && !A6 && !A5 && A4 && !A1 && !A3 && !A2)
    || (A12 && !A13 && A11 && !A10 && A9 && A8 && A7 && A6 && A5 && !A4 && !A1 && !A3 && A2);
//D6.oe = !ALEC

bool nD7 = (!A12 && !A13 && !A11 && !A10 && !A9 && !A8 && A7 && !A6 && A5 && !A4 && A1 && A3 && A2)
    || (!A12 && !A13 && !A11 && !A10 && !A9 && A8 && !A7 && A6 && !A5 && A4 && !A1 && !A3 && !A2)
    || (!A12 && !A13 && !A11 && !A10 && !A9 && A8 && !A7 && A6 && !A5 && A4 && A1 && !A3 && !A2)
    || (!A12 && !A13 && !A11 && !A10 && A9 && !A8 && !A7 && !A6 && A5 && !A4 && !A1 && A3 && A2)
    || (!A12 && !A13 && !A11 && !A10 && A9 && A8 && !A7 && !A6 && !A5 && !A4 && !A1 && !A3 && !A2)
    || (!A12 && !A13 && !A11 && !A10 && A9 && A8 && A7 && A6 && !A5 && A4 && A1 && !A3 && A2)
    || (!A12 && !A13 && !A11 && A10 && !A9 && !A8 && A7 && !A6 && A5 && A4 && !A1 && !A3 && A2);
//D7.oe = !ALEC



This code doesn't bother with the least significant bit because... well... the "Read Security PAL" code accesses the PAL with a MOVE.W instruction and an autoincrement.
That autoincrement adds two to the address.

Also, as the PAL doesn't have UDS and LDS wired up, it can't tell what the least significant bit of the address bus is.

I've a sneaking suspicion that a MOVE.B of (PAL_ADDR) and one of (PAL_ADDR+1) would result in the same value, but it would have complicated the original 68k code (would have required an ADD). Someone at Quantel knew their hardware and the 68k and decided to be a bit clever.

In any case... The 68K only presents bits A1 through A13 to the PAL. There are definitely 13 address bits (8K address space), so that bounds the loop.


(EDIT 0218: Modified the code; the PAL output makes a little more sense if you invert it like the Quantel code does before looking at it)
« Last Edit: December 03, 2016, 02:18:34 am by philpem »
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers Into PALs or GALs
« Reply #32 on: December 03, 2016, 03:15:29 am »
I've had a crack at transliterating the code into C, but I must have something massively wrong, because it doesn't work...

Code: [Select]
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

int main(void)
{
bool A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13;
bool nD0,nD1,nD2,nD3,nD4,nD5,nD6,nD7;
uint16_t Addr0[8];
uint8_t Addr1[8] = {7,7,7,7,7,7,7,0};
uint8_t InputCryptoString[64];


// LOC_52EE loop
// Start by clearing Addr0 to 0xFFFF
for (size_t n=0; n<8; n++) {
Addr0[n] = 0xFFFF;
}

// 52FC:
InputCryptoString[7] = 0; // some form of status response byte I guess?

uint16_t m68k_d1 = 0; // some kind of loop counter?
uint32_t m68k_d7 = 0; // check sum?

for (uint32_t addr = 0; addr < 0x4000; addr+=2) // This is "LoopD0"
{
A1 = (addr & 2);
A2 = !(addr & 4); // Every single GAL we have samples of seems to have A2 set as inverted in hardware
A3 = (addr & 8);
A4 = (addr & 16);
A5 = (addr & 32);
A6 = (addr & 64);
A7 = (addr & 128);
A8 = (addr & 256);
A9 = (addr & 512);
A10 = (addr & 1024);
A11 = (addr & 2048);
A12 = (addr & 4096);
A13 = (addr & 8192);

#include "12360.c"

// Read from PAL into D2
uint8_t m68k_d2 =
(!nD7 ? 128 : 0) |
(!nD6 ? 64  : 0) |
(!nD5 ? 32  : 0) |
(!nD4 ? 16  : 0) |
(!nD3 ? 8   : 0) |
(!nD2 ? 4   : 0) |
(!nD1 ? 2   : 0) |
(!nD0 ? 1   : 0);

// Invert the value
m68k_d2 ^= 0xFF;

if (m68k_d2 == 0) {
goto nextd0_d1plusplus; // TODO: Just put "d1++; continue;" here and leave that d1++ down below
}


for (uint8_t m68k_d3 = 7; m68k_d3 != 0xFF; m68k_d3--) {
if (!(m68k_d2 & (1<<m68k_d3))) {
continue;
}

//uint16_t m68k_d4 = m68k_d3 << 1;
uint16_t m68k_d5 = Addr0[m68k_d3];
Addr0[m68k_d3] = m68k_d1;

// 005328   NEG.W D5
m68k_d5 = -m68k_d5;

// ADD.W D1, D5
m68k_d5 += m68k_d1;

// SUBQ #1, D5
m68k_d5--;

// CMPI.W #$800, D5
if (m68k_d5 >= 0x800) {
printf("CleanVarsAndReturn :(\n");
return -1;
}

// BTST #$A, D5  -->  and by 1024
if (m68k_d5 & 0x400) {
// >= 2KB
InputCryptoString[7] ^= m68k_d3;
printf("More than 2KB; CleanVarsAndReturn :(\n");
return -1;
}

// lessThan2KB:
// 005344:
uint16_t m68k_d6 = Addr1[m68k_d3];
if (m68k_d6 >= 7) {
printf("Addr1 >= 7, CleanVarsAndReturn :(\n");
return -1;
}

// 005350:
if (m68k_d5 & 0x200) {
// 005356: bset d3, (a0,d6.w)
InputCryptoString[m68k_d6] |= (1<<m68k_d3);
} else {
// 00535C: bclr d3, (a0,d6.w)
InputCryptoString[m68k_d6] &= ~(1<<m68k_d3);
}

// 005350:
m68k_d6 <<= 3;
m68k_d6 += m68k_d3;
if (m68k_d5 & 0x100) {
// 00536A:
m68k_d7 |= 1<<m68k_d6;
}

// 00536C:
InputCryptoString[m68k_d6 + 8] = m68k_d5;
Addr1[m68k_d3]++;
}

nextd0_d1plusplus:
m68k_d1++;
}


printf("DONE -->\n");
printf("Addr0: ");
for (size_t i=0; i<8; i++) {
printf("%u ", Addr0[i]);
}
printf("\n");

printf("Addr1: ");
for (size_t i=0; i<8; i++) {
printf("%u ", Addr1[i]);
}
printf("\n");
}

It looks like the code (ab)uses parts of the Crypto String for other things. The 8th character (which gets cleared at 0052FC) is some form of status code; 00533C flips a bit in that if something is wrong.

First seven bytes of the Crypto String could well be the serial number.

More when I've had some sleep.
« Last Edit: December 03, 2016, 03:18:30 am by philpem »
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline dexters_labTopic starter

  • Supporter
  • ****
  • Posts: 1891
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #33 on: December 03, 2016, 08:31:18 am »
wow, thanks for all the help everyone.

to answer some of your earlier questions, the system is a 68010

The end result hopefully is to reprogram serial number 10352, because it's for this that i have a full set of license keys for a 'Harriet' system.

i have placed a copy of all the files in question on my google drive as public:
https://drive.google.com/drive/folders/0B24DgvumEJXibEgzRElOVjhCT1U?usp=sharing

the .JED files are the securityPALs that i managed to read. In the box of ICs i found there where a couple of duplicates these were 12360_A & B & 15462_A & B. I only included the copy to be complete. The JED files marked 17624_1, 17624_2 & 17624_N seem to be slightly different versions of 17624, they are marked like that on the IC label.

I also included the i64 & asm files that Nicolas did of the EPROM disassembly, there are copies of the actual EPROMs there too.

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #34 on: December 03, 2016, 11:45:35 am »
After a little more playing... I've fixed my C version of the function:

All those PALs seem to fail the "Read Security PAL" routine, or at least my C translation of it.

I've attached the C versions of the PAL equations.

I'm starting to suspect JED2EQN might have screwed up. To rule that out, I think my next step would be to make up an adapter and read one out as an EPROM -- that'd give a... what, 8K binary file?

Have you got an EPROM programmer, Dexter? (I do, but I suspect you might not want to part with one of your security PALs!)

The adapter you want to make would wire /OE to /ALEC, the address lines straight through (EPROM A0 to PAL A1, A1 to A2 and so on), the data lines 1:1, and VCC and ground straight through. That should allow an EPROM programmer to read the PAL as if it were a ROM.

In the meantime -- I'll see if I can get the "checking routine" going in EASy68k.

Cheers,
Phil.
« Last Edit: December 03, 2016, 11:48:10 am by philpem »
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline dexters_labTopic starter

  • Supporter
  • ****
  • Posts: 1891
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #35 on: December 03, 2016, 12:34:02 pm »
Yes i have a TL866, thats what i used to read the GAL/PALs.

I can try wiring it up as a rom, a couple of people mentioned doing that before

I can send you one of the ones i can read if you want, they are only useful to this exercise as i dont have the software keys for these serials anyway

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #36 on: December 03, 2016, 02:14:49 pm »
Yes i have a TL866, thats what i used to read the GAL/PALs.

I can try wiring it up as a rom, a couple of people mentioned doing that before

I can send you one of the ones i can read if you want, they are only useful to this exercise as i dont have the software keys for these serials anyway

Hold off on that for the minute, I think. I've got Quantel's function running in EASy68k. There's definitely something up with my C code.

For the 12360 PAL, whatever intermediate data you get out of said PAL, decrypted with the "Copyright Quantel" string, nets you the null-terminated string "HARRIET". Very interesting!

It looks like the addresses of the bytes in the address map encode a text string (possibly encoded further by the serial number - I haven't got to the encryption yet), and the specific values (somehow) encode the serial number.

It's sneaky, I'll give them that!
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #37 on: December 03, 2016, 03:34:23 pm »
Fixed it!

After a couple of hours sat with EASy68k, I figured out what I was doing wrong. This little gem emulates the Quantel "Read and verify Security PAL" function.

You get two things out of that function, by the way:

  * The "Crypted String" is a decrypted version of what's stored in the the PAL. The format of that is -- serial number twice, then a text string.
  * A return code, either "success" or "failure".

Addr0 and Addr1 are just internal state, kept on the stack.

There are two checksums: one on the encrypted string, one on the decrypted string. They're just additive, so nice and easy to recalculate.

Reversing the encryption to get from a "decrypted string" to an "encrypted string" (or vice versa) is easy too: XOR is commutative.

The only problem left to solve is figure out how the data in the PAL is encoded, then we can -- in theory -- make new PALs.

But with what we have now, given a chunk of arbitrary PAL data (as if we read the PAL out in an EPROM programmer) or a set of PAL equations reformatted into C, we can answer the question "would a Paintbox accept this as a valid PAL?".


EDIT: Now I've done some more mods to my tool... I can tell you what the PALs are for!

12360: Harriet
15462-E: PFrame Paintbox
17624-1: Editbox
17624-2: Editbox
17624-N: Network
« Last Edit: December 03, 2016, 03:48:30 pm by philpem »
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 
The following users thanked this post: dexters_lab

Offline dexters_labTopic starter

  • Supporter
  • ****
  • Posts: 1891
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #38 on: December 03, 2016, 03:51:22 pm »
awesome work philpem!  :-+

your results tie in to a couple of other small points i was about to make...

firstly the two serial numbers. On these machines they have a machine serial number and a group serial number. I think so a group of machines can share a license key. But if there is no group then these two numbers are the same.

secondly, during boot of the main OS just after it reports the serial numbers there is a description in caps, on machine 10352 it's 'PAINTBOX', it's quite possible this is the same text you found that said 'HARRIET'

you can see a short excerpt from the console output here:

Code: [Select]
AFS V-series Diagnostics and Painting, version "V8-18-001 Paintbox"
Built on  3-MAY-1994 at 11:21:28.09, file version V7.01
Software generated for Paintbox        1-00
NTSC standard 2133, 1940
Serial number: 10352, 12742

Description  : PAINTBOX         

i am going to have a play wiring the PAL up as a PROM and see if i can get my TL866 to read the PAL that is coded with 12360


Offline dexters_labTopic starter

  • Supporter
  • ****
  • Posts: 1891
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #39 on: December 03, 2016, 04:00:09 pm »

EDIT: Now I've done some more mods to my tool... I can tell you what the PALs are for!

12360: Harriet
15462-E: PFrame Paintbox
17624-1: Editbox
17624-2: Editbox
17624-N: Network

Harriet and Paintbox are essentially the same thing, a Harriet is a Paintbox with all the extras. Editbox is their non-linear digital editing machine

many paintboxes, editboxes etc had a network or other interface board fitted inside the machine, these have their own SecurityPAL on which is why there is a separate PAL for that. I am not sure why there are two PALs for the editbox though but i don't know too much about these systems so...  :-//


Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #40 on: December 03, 2016, 04:20:59 pm »
awesome work philpem!  :-+

your results tie in to a couple of other small points i was about to make...

firstly the two serial numbers. On these machines they have a machine serial number and a group serial number. I think so a group of machines can share a license key. But if there is no group then these two numbers are the same.

Ahh - that makes sense. That sounds like what's known as a "floating licence" these days. You network a bunch of machines and buy, say, one licence for "advanced retouching" tied to the group. If someone's using it -- well, the other person who wants to use it has to wait until the other person finishes.

It works fairly well, as long as the "pool" is reasonably sized.


secondly, during boot of the main OS just after it reports the serial numbers there is a description in caps, on machine 10352 it's 'PAINTBOX', it's quite possible this is the same text you found that said 'HARRIET'

you can see a short excerpt from the console output here:

Code: [Select]
AFS V-series Diagnostics and Painting, version "V8-18-001 Paintbox"
Built on  3-MAY-1994 at 11:21:28.09, file version V7.01
Software generated for Paintbox        1-00
NTSC standard 2133, 1940
Serial number: 10352, 12742

Description  : PAINTBOX         

Well, that looks like a smoking gun if I ever saw one.

At a guess, the second instance of the serial number is probably the "group" serial number then, but without seeing an image of a PROM with a group serial number, I couldn't say for certain.



i am going to have a play wiring the PAL up as a PROM and see if i can get my TL866 to read the PAL that is coded with 12360

Definitely do. I've attached a copy of the 12360 binary I generated from the equations.

If you can get a good read from this one, it's probably worth trying to read out the other PALs you have -- even the ones with the security bit set should read out OK as PROMs. With that information, it should be simple enough to bash together a tool to create PAL equations for them.

Or, indeed, feed them into QUAN_ALG and see what the contents are.




EDIT: Now I've done some more mods to my tool... I can tell you what the PALs are for!

12360: Harriet
15462-E: PFrame Paintbox
17624-1: Editbox
17624-2: Editbox
17624-N: Network

Harriet and Paintbox are essentially the same thing, a Harriet is a Paintbox with all the extras. Editbox is their non-linear digital editing machine

many paintboxes, editboxes etc had a network or other interface board fitted inside the machine, these have their own SecurityPAL on which is why there is a separate PAL for that. I am not sure why there are two PALs for the editbox though but i don't know too much about these systems so...  :-//

At a guess - maybe Editbox needs two machines linked, and you have one PAL in each machine?
Who knows.  :-//

Cheers.
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 10088
  • Country: us
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #41 on: December 03, 2016, 04:51:20 pm »
I don't see why the SecurityPAL couldn't implement some kind of FSM since there is a CPU involved.  It could certainly be coded as an 8 bit wide LFSR with the CPU mangling the address lines based on previous output from the PAL.

If I had a working system, I would be connecting a logic analyzer to the PAL.  I would think that, given the possibility of a LFSR in uC code, trying to simply decode the PROM would be an exercise in futility.
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #42 on: December 03, 2016, 05:03:41 pm »
I don't see why the SecurityPAL couldn't implement some kind of FSM since there is a CPU involved.  It could certainly be coded as an 8 bit wide LFSR with the CPU mangling the address lines based on previous output from the PAL.

If I had a working system, I would be connecting a logic analyzer to the PAL.  I would think that, given the possibility of a LFSR in uC code, trying to simply decode the PROM would be an exercise in futility.

You're a touch late to the party  ;D

We've got the PAL equations from a couple of unlocked chips and we've got a schematic -- those ruled out any register-related FSM shenanigans. The disassembly ruled out any similar foul play in the software.

It's just a crazily-encoded ROM.
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 14786
  • Country: gb
    • Mike's Electric Stuff
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #43 on: December 03, 2016, 05:09:11 pm »
I don't see why the SecurityPAL couldn't implement some kind of FSM since there is a CPU involved.  It could certainly be coded as an 8 bit wide LFSR with the CPU mangling the address lines based on previous output from the PAL.
Except that the limited number of product-terms available would limit functionality substantially, and it would be hard to generate a decent number space that was guaranteed to be fittable in the device.
Later PLD devices, in particular the PEEL series would be a lot better as they can have buried internal registers to substantially obfuscate operation. Even the 22V10 can't hide the contents of its internal registers as far as I can recall, as feedback comes after the output-enable'd buffers, though there would be some scope to make it harder to figure out by making functions dependent on (visible) output states.
With a 20L8 , you can't  do anything more than make it act like a PROM with a finite number of either '1' or '0' outputs. The encoding of the address is at least as likely to be to make better use of the available capacity as for any kind of security.


Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline dexters_labTopic starter

  • Supporter
  • ****
  • Posts: 1891
  • Country: gb
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #44 on: December 03, 2016, 05:41:41 pm »
philpem,

attached is a .bin of the 12360 PAL as read as a TMS2764 EPROM, it does appear to be a match for your file but if you could double check?

i am quite shocked my wiring worked TBH!

Offline stfsux

  • Contributor
  • Posts: 23
  • Country: 00
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #45 on: December 03, 2016, 06:20:01 pm »
I've just quick questions about EQN.
D0 to D7 are High-Z as long as ALEC is high, right?
Code: [Select]
/d0 = /a12 ...
This means, d0 is low if the logic equation is true? But if it's not the case, there are High-Z?
Or they have a default state when you declare them?
 

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 14786
  • Country: gb
    • Mike's Electric Stuff
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #46 on: December 03, 2016, 06:26:03 pm »
All output enables are controlled by pin 23 ( the .oe term)
The equations are booleans - AND all terms on each line then OR the result. there may be an inversion on the output.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #47 on: December 03, 2016, 06:30:32 pm »
philpem,

attached is a .bin of the 12360 PAL as read as a TMS2764 EPROM, it does appear to be a match for your file but if you could double check?

i am quite shocked my wiring worked TBH!

Harriet says hello!  ;D
Looks good to me!

Code: [Select]
Loaded 12360_Read_As-ROM.bin

DONE -->

Crypted string:   --0H--0HHARRIET                 --------------------------------

Hex crypted string:
  00 00 30 48 00 00 30 48 48 41 52 52 49 45 54 20
  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
  00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 00
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Crypted string checksum (D7 loword): 5176

Clear string checksum   (D7 hiword): 1319

AddrBitLastSeen aka Addr0                    (internal): 3683 2946 2451 1679 2692 4128 3568 600

BitActiveCounter aka Addr1 - should all be 7 (internal): 7 7 7 7 7 7 7 7

I dare say you could swap in a couple of other PALs if you wanted to.



I've been working on cleaning up and figuring out the PAL readback code too...

  * The decoder counts how many Pterms are associated with each bit -- that is, how many addresses it sees a bit respond to. The answer must be seven (not six, unless thou then proceedeth to seven, and eight is right out).
    * Overflowing this will cause the check to fail without any checksumming or decryption being performed.
    * In short: You have seven Pterms and eight bits, or 56 Pterms. Use them wisely.

  * "Commands" are encoded as the deltas between the bit addresses.
    * That is, every time an 'active' Pterm is seen, the decoder calculates the distance between this address and the last time it saw this bit respond.
    * The "last time" array is set to -1 (well, 0xFFFF but who's counting?) at the start.
 
  * A command (delta) of 0x800 or greater will cause an error.

  * Command AND 0x400? Inverts the current bit of Crypto String character 7.
    * Character 7 is zeroed at the start of the loop.
    * If this happens more than once, it's an error. TL/DR: you can set bits in character 7, but not clear them.
    * Char 7 is the last byte of the serial number...

  * Command AND 0x200? Sets the current serial number bit.

  * Command AND 0x100? Sets a bit in the checksum word.
    * This is done in a slightly peculiar way. Take the index of the Pterm bit in the PAL which triggered,

  * Oh, and for good measure, store the least significant 8 bits of the command in CryptoString[8+ ((NumberOfTimesIveSeenThisBit * 8) + BitNumber)]
    * BitNumber = 7..0 for bit mask 128..1


In summary... it stores a bunch of data as delta-encoded commands, whose meaning changes depending on which bit they happened to be seen on.

I've done a hell of a lot of commenting on this thing, results are attached. Phew.

Next up is "How the bleeding hell do I make a PROM to satisfy this thing?!"
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 14786
  • Country: gb
    • Mike's Electric Stuff
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #48 on: December 03, 2016, 06:43:25 pm »
Next up is "How the bleeding hell do I make a PROM to satisfy this thing?!"

Brute force ?
There are a lot of permutations but a lot of them can be eliminated.
Looks like the scheme was designed to make it hard, but computing power has come rather a long way since it was designed.
Obvious constraints :
One bit at a time, obviously
Only 7 of 8192 possible addresses can be active.
That's rather a big space, but I'm sure there must be ways to eliminate some based on other constraints.




Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline philpem

  • Frequent Contributor
  • **
  • Posts: 340
  • Country: gb
  • That Sneaky British Bloke
Re: Reversing The Encoding Of Serial Numbers In PALs or GALs
« Reply #49 on: December 03, 2016, 07:27:43 pm »
Next up is "How the bleeding hell do I make a PROM to satisfy this thing?!"

Brute force ?
There are a lot of permutations but a lot of them can be eliminated.
Looks like the scheme was designed to make it hard, but computing power has come rather a long way since it was designed.
Obvious constraints :
One bit at a time, obviously
Only 7 of 8192 possible addresses can be active.
That's rather a big space, but I'm sure there must be ways to eliminate some based on other constraints.

I think there are defined places where things need to go. In other words, if you want a certain block of PROM data, there are only a few ways to encode it.

Ultimately what you're encoding in the address deltas are three command bits, plus the description. The command bits are effectively "subchannel" or "subcarrier" data: they're there so that in addition to those 56 bits which are used to say "This delta is the one which encodes the character code for this byte in the Description field", you can encode the serial number and checksum fields into the data too -- by increasing the space between some of the bits.

56 characters to encode, 8 bits to stuff them in... 7 product terms... Lucky seven, again.

I've got some ideas, mostly centred around a version of QUAN_ALG with the checksum tests disabled. Get the description encoding working, then start encoding other things.
Phil / M0OFX -- Electronics/Software Engineer
"Why do I have a room full of test gear? Why, it saves on the heating bill!"
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->