/* * DecoEm -- Videocrypt decoder emulator for DOS. * * This software demonstrates, how a PC can communicate * with a Videocrypt smartcard over the serial port. The * hardware required is the PC<->card adapter as described in * ADAPTER.TXT. This program can also be used for building * software which communicates with other ISO 7816 conforming * smartcards that use the T=0 protocol. * * Markus Kuhn -- 1994-11-21 * * Compiles e.g. with Borland C++ 2.0, small model. * Link with ASYNC.OBJ as it comes with e.g. Season7 1.3. */ #include #include #include #include #include #include /* for kbhit() & clrscr() */ #include /* for int86() */ #include "async.h" #define CLK 3.571e6 /* CLK frequency in Hz delivered by adapter */ #define SECOND 1000000L /* one second in microseconds */ #define RESET_ANSWER_MAX 33 char reset_answer[RESET_ANSWER_MAX] = { 0x3f }; int reset_length = 0; /* message directions */ #define IN 0 /* to decoder */ #define OUT 1 /* to card */ /* * These are some delay values (microseconds) used in the program. * Use command line option w to modify them. */ #define DELAYS 1 unsigned long delus[DELAYS] = { 1000, /* wait after each outgoing byte */ }; /* * Reverse and invert all bits in each byte of a string. * E.g. 10010111b (0x97) becomes 00010110b (0x16). */ void inverse(char *data, int len) { int i, j; unsigned char c; for (i = 0; i < len; i++) { c = 0; for (j = 0; j < 8; j++) c |= ((data[i] & (1 << j)) != 0) << (7 - j); data[i] = ~c; } return; } /* * Uses a BIOS function in order to wait a specified number of * microseconds. This BIOS functions is only implemented in * IBM AT compatible BIOSes (i.e., not in old PC and XT systems)! * The timing resolution of this function isn't really down * to 1 us on most systems, but it's fine for our purposes. */ void wait_us(unsigned long microseconds) { union REGS regs; if (microseconds == 0) return; regs.h.ah = 0x86; regs.x.cx = (int) (microseconds >> 16); regs.x.dx = (int) (microseconds & 0xffff); int86(0x15, ®s, ®s); return; } void activate_com(int com) { int port, parity; switch (com) { case 1: port = COM1; break; case 2: port = COM2; break; case 3: port = COM3; break; case 4: port = COM4; break; default: printf("Port COM%d not available!\n", com); exit(1); } if (AsyncInit(port)) { printf("Can't initialize port COM%d!\n", com); exit(1); } AsyncHand(DTR | RTS); parity = (reset_answer[0] == 0x3f) ? ODD_PARITY : EVEN_PARITY; AsyncSet(9600, BITS_8 | STOP_2 | parity); return; } void deactivate_com(void) { AsyncHand(DTR | RTS); /* reset low */ AsyncStop(); fprintf(stderr, "closed.\n"); } /* * Send a string of bytes to serial port and wait for each single echo. * (More efficient send methods are too fast for decoder, it seems to * need more than 2 stop bits, so we wait between individual bytes.) */ int send(char *data, int len) { int i; char c; AsyncClear(); for (i = 0; i < len; i++) { c = data[i]; if (reset_answer[0] == 0x3f) inverse(&c, 1); AsyncOut(c); while (AsyncInStat() == 0); AsyncIn(); #if 0 wait_us(delus[0]); #else delay(1); #endif } return 0; } /* * Get all bytes available from serial port FIFO and return how many * bytes were available. */ int receive(char *data, int max) { int i = 0; while (AsyncInStat() > 0 && i < max) data[i++] = AsyncIn(); if (reset_answer[0] == 0x3f) inverse(data, i); return i; } /* * Get n bytes from serial port FIFO. Don't wait more than timeout * microseconds and return how many bytes were available. */ int receive_timeout(char *data, int n, long timeout) { int i = 0; while (i < n && timeout > 0) if (AsyncInStat() > 0) data[i++] = AsyncIn(); else { wait_us(15000); timeout -= 15000; } if (reset_answer[0] == 0x3f) inverse(data, i); return i; } /* * Perform a card reset. Try up to 10 times. * * Return value: 0 correct answer to reset received * -1 reset failed */ int reset(void) { int fail_count = 0; int y, p, i, err, tck_expected; int parity; reset_length = 0; AsyncClear(); while (fail_count < 10) { AsyncHand(DTR | RTS); /* reset low */ wait_us(45000.0 / CLK * 1e6); /* wait initial CLK cycles */ AsyncClear(); AsyncHand(DTR); /* reset high */ wait_us(45000.0 / CLK * 1e6); /* wait for answer */ /* Read TS */ if (AsyncInStat() != 0) { reset_answer[0] = AsyncIn(); if (reset_answer[0] != 0x3b) inverse(reset_answer, 1); if (reset_answer[0] == 0x3f || reset_answer[0] == 0x3b) { if (AsyncInStat() < 1) wait_us(1000000L); /* Read T0 */ if (receive(reset_answer + 1, 1) == 1) { tck_expected = 0; err = 0; p = 2; /* number of bytes read so far */ /* Read TA_i, TB_i, TC_i, TD_i */ y = reset_answer[p - 1] >> 4; while (!err && y && p < RESET_ANSWER_MAX - 5 - (reset_answer[1] & 15)) { for (i = 0; !err && i < 4; i++) { if ((y >> i) & 1) { if (AsyncInStat() < 1) wait_us(1000000L); err |= receive(reset_answer + p, 1) != 1; if (i == 3) { tck_expected |= (reset_answer[p] & 15) != 0; y = reset_answer[p] >> 4; } p++; } else if (i == 3) y = 0; } } /* historic characters */ if (!err) { if (AsyncInStat() < (reset_answer[1] & 15)) wait_us(1000000L); err |= receive(reset_answer + p, reset_answer[1] & 15) != (reset_answer[1] & 15); } p += (reset_answer[1] & 15); /* TCK */ if (!err && tck_expected) { if (AsyncInStat() < 1) wait_us(1000000L); err |= receive(reset_answer + p, 1) != 1; p++; y = 0; for (i = 1; i < p; i++) y ^= reset_answer[i]; err |= y != 0; } /* everything was fine */ if (!err) { reset_length = p; parity = (reset_answer[0] == 0x3f) ? ODD_PARITY : EVEN_PARITY; if ((reset_answer[1] & 0x10) != 0 && reset_answer[2] == 0x31) /* we can't double CLK freq., but we can reduce baud rate */ AsyncSet(4800, BITS_8 | STOP_2 | parity); else if ((reset_answer[1] & 0x10) != 0 && reset_answer[2] == 0x13) AsyncSet(38400L, BITS_8 | STOP_2 | parity); else AsyncSet(9600, BITS_8 | STOP_2 | parity); return 0; } } } } fail_count++; } AsyncHand(DTR | RTS); /* reset low */ return -1; } /* * send a command (T=0 protocol) to the card and send/receice data. * * Return value: 0 command succeded * -1 command failed */ int command(int instruction, unsigned char *data, int length, int direction) { char header[5] = {0x53, 0x00, 0x00, 0x00, 0x00}; char pb; /* procedure byte */ char sw2; int i = 0; AsyncClear(); length &= 0xff; instruction &= 0xfe; if ((instruction & 0xf0) == 0x60 || (instruction & 0xf0) == 0x90 || (instruction & 1) != 0) return -1; header[1] = instruction; header[4] = length; if (length == 0 && direction == IN) length = 256; send(header, 5); do { do { if (receive_timeout(&pb, 1, 2 * SECOND) != 1) { fprintf(stderr, "procedure byte timeout, %d data bytes so far.\n", i); return -1; } } while (pb == 0x60); if (((pb ^ instruction) & 0xfe) == 0) { if (direction == OUT) for (; i < length; i++) send((char *) data + i, 1); else for (; i < length; i++) { if (receive_timeout((char *) data + i, 1, 2 * SECOND) != 1) { fprintf(stderr, "data byte timeout, %d data bytes so far.\n", i); return -1; } } } else if (((pb ^ instruction) & 0xfe) == 0xfe) { if (direction == OUT) send((char *) data + i, 1); else if (receive_timeout((char *) data + i, 1, 2 * SECOND) != 1) { fprintf(stderr, "data byte timeout, %d data bytes so far.\n", i); return -1; } i++; } else { if ((pb & 0xf0) == 0x60 || (pb & 0xf0) == 0x90) { if (receive_timeout(&sw2, 1, 2 * SECOND)) fprintf(stderr, "SW2 timeout.\n"); fprintf(stderr, "SW1 SW2 = %02x %02x, %d data bytes so far.\n", (unsigned char) pb, (unsigned char) sw2, i); } else fprintf(stderr, "illegal procedure byte %02x, " "%d data bytes so far.\n", (unsigned char) pb, i); return -1; } } while (i < length); /* final procedure bytes */ do { if (receive_timeout(&pb, 1, 2 * SECOND) != 1) { fprintf(stderr, "final procedure byte timeout.\n"); return -1; } } while (pb == 0x60); if ((pb & 0xf0) == 0x60 || (pb & 0xf0) == 0x90) if (receive_timeout(&sw2, 1, 2 * SECOND) != 1) { fprintf(stderr, "SW2 timeout.\n"); return -1; } if ((unsigned char) pb != 0x90 || sw2 != 00) { fprintf(stderr, "SW1 SW2 = %02x %02x.\n", (unsigned char) pb, (unsigned char) sw2); return -1; } return 0; } /* * This procedure sends a 32-byte message using a 0x74 instruction to * a Videocrypt smartcards and requests the 8-byte answer with a * 0x78 instruction. Only useful for Videocrypt cards. * * Return value: 0 card answered with 00 00 00 00 00 00 00 00 * or 01 00 00 00 00 00 00 00 * 1 card answered in the 0x78 instruction with * something more intetesting. */ int query(unsigned char *msg, unsigned char *answ) { const int maxfailure = 4; int i, failures=0, trouble=0, ok; do { trouble = command(0x74, msg, 32, OUT); if (trouble) fprintf(stderr, "command 74h failed.\n"); else if (msg[0] & 8) { trouble = command(0x78, answ, 8, IN); if (trouble) fprintf(stderr, "command 78h failed.\n"); } if (trouble) { failures++; while (failures <= maxfailure && trouble) { if (reset()) { fprintf(stderr, "card reset failed.\n"); failures++; } else trouble = 0; } if (failures > maxfailure) { fprintf(stderr, "aborting after %d failures.\n", maxfailure); exit(1); } trouble = 1; } } while (trouble); ok = ((answ[0] & 0xfe) != 0); for (i = 1; i < 8; i++) ok |= (answ[i] != 0); return ok; } /* * Main program -- nothing interesting happens here, replace it with * whatever you need. */ main() { int com = 2; int i; unsigned char serial[6]; unsigned char msg[32]= { /* an example 0x74 message */ 0xe8, 0x43, 0x66, 0x3e, 0xc6, 0x1a, 0x0c, 0x9f, 0x8f, 0x3f, 0x7d, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00 }; unsigned char answ[8]; fprintf(stderr, "\nDecoEm -- Videocrypt decoder emulator " "-- Markus Kuhn\n\n"); fprintf(stderr, "Using serial port COM%d, byte delay %lu \xe6s.\n\n", com, delus[0]); activate_com(com); if (atexit(deactivate_com)) fprintf(stderr, "Can't call atexit()!\n"); printf("RESET\n"); if (reset()) { fprintf(stderr, "card reset failed.\n"); #if 0 exit(1); #endif } else { printf("answer to reset:\n"); for (i = 0; i < reset_length; i++) printf("%02x%c", (unsigned char) reset_answer[i], ((i & 15) == 15) ? '\n' : ' '); } printf("\n\n"); printf("command 70h (get serial number):\n"); if (command(0x70, serial, 6, IN)) { fprintf(stderr, "command failed.\n"); exit(1); } for (i = 0; i < 6; i++) printf("%02x ", serial[i]); printf("\n\n"); printf("card issue: %02d, card serial number: %08lu_\n\n", serial[0] & 0x0f, ((unsigned long) serial[1] << 24) | ((unsigned long) serial[2] << 16) | ((unsigned long) serial[3] << 8) | (unsigned long) serial[4]); printf("command 74h (send 32 bytes to card):\n"); if (command(0x74, msg, 32, OUT)) { fprintf(stderr, "command failed.\n"); exit(1); } for (i = 0; i < 32; i++) printf("%02x%c", msg[i], ((i & 15) == 15) ? '\n' : ' '); printf("\n"); printf("command 78h (receive 8 seed bytes from card):\n"); if (command(0x78, answ, 8, IN)) { fprintf(stderr, "command failed.\n"); exit(1); } for (i = 0; i < 8; i++) printf("%02x ", answ[i]); printf("\n"); return 0; }