2012-04-11 @ 2026 MDT -- Receiving packets with egg from a home grown CC1101 board
2012-04-10 @ 2034 MDT -- Packets Captured!
2012-04-09 @ 2034 MDT -- Complete Config Capture
2012-04-04 @ 2114 MDT -- Dumped CC1101 Register Settings
2012-03-20 @ 2200 MDT -- Sensor μC pinout
2012-03-17 @ 2010 MDT -- Decoding packets....
2012-03-16 @ 2137 MDT -- Have working altered binary!
The greenGOOSE [http://www.greengoose.com] start kit ships with four wireless sensors, a green base station egg, power supply, etc. The cost of the kit was $49 USD at the time I purchased it. Considering the hardware received this is a pretty good deal -- Comparable CC1101 transceiver boards on ebay go for upwards of $10 USD.
Interesting Components: PIC 18F25J10 µC, ENC28J60 ethernet controller |
Interesting Components: Ti CC1101 ISM Transceiver. PIC Programming Header (J2 -- see below) |
Before beginning to customize the base station I thought it prudent to backup the stock firmware. This was done by attaching test leads to the PGD, MCLR, PGC, VDD, and VSS lines on the 18F25J10. Other than a few scattered test points, the only likely place for a programming header was J2. After a quick probing, it turned out that J2, was in fact, the programming header.
|
Next, I pulled straight up on the plastic housing of J2. I knocked off the pins with a hot iron and put on test leads on pins 1-5.
Turns out that the Pickit2 pinout mapped exactly to the pinout for J2.
I jumpered all together, fired up the PICkit 2 Programmer software, and was able to read the flash.
Interesting strings found in the hex dump:
Content-Length: 54 |
POST /cgi-bin/xxxxxxxxxx.cgi |
HTTP/1.1 |
Host: |
xxx.xxxxxxxxxx.com |
HTTP/1.1 200 OK |
Connection: close |
After determining that the gg egg was using an HTTP post, I could have fired up wireshark or spoofed DNS responses but that would be no fun. Simply modifying the dumped binary to point to my own backend seemed like the way to go.
The URL to which the gg egg posts data is a 19 byte null terminated string which can be located in the intel hex dump at:
:1077E000 3A20636C6F736500 START HERE ->6170...
:1077F000 ...E636F6D00 <- END HERE FF056A046AB5
I was lucky enough to have a URL with exactly the same number of characters as xxx.xxxxxxxxxx.com --> bozeman.dyndns.org If you are not so lucky, I'm sure it would work to stay under 18 characters and simply pad-out with nulls. Below is a quick and dirty perl snippet to calculate the new intel hex checksum for the line you alter.
#!/usr/bin/perl -w use strict; my $bytes = shift or die "$!"; die "Please specify an even number of nibbles!\n" if length($bytes) % 2; my $sum = 0; for(my $i = 0; $i < length($bytes); $i += 2){ $sum += ord(pack("H2", substr($bytes, $i, 2))); } $sum &= 0x000000FF; $sum = 0x00000100 - $sum; printf("%02X\n", $sum);
My altered code that changes xxx.xxxxxxxxxx.com to bozeman.dyndns.org is below:
:1077E0003A20636C6F736500626F7A656D616E2E0F :1077F00064796E646E732E6F726700FF056A046AA7
Next, I loaded up the altered binary in the Pickit2 software, and loaded it into the egg. Success.
I, almost immediately, began to see entries in my webserver log:
10.0.9.1 bozeman.dyndns.org - [16/Mar/2012:21:22:33 -0600] "POST /cgi-bin/gglistener.cgi HTTP/1.1" 500 369 "-" "-" 10.0.9.1 bozeman.dyndns.org - [16/Mar/2012:21:22:43 -0600] "POST /cgi-bin/gglistener.cgi HTTP/1.1" 500 369 "-" "-" 10.0.9.1 bozeman.dyndns.org - [16/Mar/2012:21:22:53 -0600] "POST /cgi-bin/gglistener.cgi HTTP/1.1" 500 369 "-" "-" 10.0.9.1 bozeman.dyndns.org - [16/Mar/2012:21:23:03 -0600] "POST /cgi-bin/gglistener.cgi HTTP/1.1" 500 369 "-" "-" 10.0.9.1 bozeman.dyndns.org - [16/Mar/2012:21:23:13 -0600] "POST /cgi-bin/gglistener.cgi HTTP/1.1" 500 369 "-" "-"
The initial cgi did not exist. Hence the 500 error code. Also, interestingly, the LED was red. After stubbing out a quick test cgi:
#!/usr/bin/perl -w use strict; use CGI; my $text; open(OUT, ">>/tmp/gg.log"); if($ENV{'CONTENT_LENGTH'}){ read(STDIN,$text,$ENV{'CONTENT_LENGTH'}); my $bytes = unpack("H*", $text); print OUT time() . " -- " . $bytes . "\n"; } print "Connection: close\n\n";
the LED was still red although I began to receive data in my cgi's log:
1331954948 -- 474700e1050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331954953 -- 474700e2050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331954958 -- 474700e3050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331954963 -- 474700e4050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331954968 -- 474700e5050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Next, I tweaked the cgi script to reply properly and with "AOK":
#!/usr/bin/perl -w use strict; use CGI; my $text; open(OUT, ">>/tmp/gg.log"); if($ENV{'CONTENT_LENGTH'}){ read(STDIN,$text,$ENV{'CONTENT_LENGTH'}); my $bytes = unpack("H*", $text); print OUT time() . " -- " . $bytes . "\n"; } print "\n\nAOK\n"; print "Connection: close\n\n";
The LED turned green, and the data stopped coming every 5-10 seconds and began to come every 25-30 seconds. I assume these are "check-in" messages.
1331955133 -- 47470108050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331955148 -- 4747010a050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331955158 -- 4747010b050200000050c000000000000d5000002XXXf28b000000030002XXXf00000000000000000000000000000000000000000000 1331955182 -- 4747010c050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331955207 -- 4747010d050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 1331955232 -- 4747010e050100000050c000000000000011ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
I also shook the treat sensor and received, what I presume to be, messages from it:
1331954970 -- 474700e6050200000050c00000000000256000002XXX4c00101d4814d00444161004401690063c1690053816d0033464100831549006 1331954971 -- 474700e7050200000050c000000000000d5000002XXX74870000000000003bab00000000000000000000000000000000000000000000
Okay, time to start looking at the packets. From what we've learned it looks like we're dealing with a fixed record length message encoding as indicated by the Content-Length: 54 HTTP header....
Check-In Packet | ||
---|---|---|
1 2 3 4 5 | ||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 | ||
474700 82 05010000 0050c0000000 00000011 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | ||
474700 83 05010000 0050c0000000 00000011 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | ||
Byte Offset | Value | Meaning |
0-2 | 474700 | Header -- ASCII String: GG (greengoose) |
3 | 82 | Packet Counter 0 through 255 |
4-7 | 05010000 | Message Type (in this case, check-in) |
8-13 | 0050c0000000 | Egg's Ethernet MAC Address (unique identifier) |
14-17 | 00000011 | Message Cause? 11 == check-in? |
17-53 | ffff...ff | Unused Buffer Space |
Sensor Packet | ||
---|---|---|
1 2 3 4 5 | ||
Timestamp (cgi) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 | ||
11332041003 -- 474700 ec 05020000 0050c0000000 00000d60 00002XXX a4001000a09e10001b9a00000000000000000000000000000000000000000000 | ||
11332041003 -- 474700 ed 05020000 0050c0000000 00000d50 00002XXX 72a60001000000000c9d00000000000000000000000000000000000000000000 | ||
11332041010 -- 474700 ee 05020000 0050c0000000 00000960 00002XXX a80010005dd70000000000000000000000000000000000000000000000000000 | ||
0-2 | 474700 | Header -- ASCII String: GG (greengoose) |
3 | 82 | Packet Counter 0 through 255 |
4-7 | 05010000 | Message Type (in this case, check-in) |
8-13 | 0050c0000000 | Egg's Ethernet MAC Address (unique identifier) |
14-17 | 00000d60 | Message Cause? d60 == start action, d50 == end action? 960 == ? |
18-21 | 00002XXX | Sensor Id. Each of my 4 sensors has a unique id in this field |
22-53 | ... | Accelerometer data? Packet signal data? Debounce time data? |
From here, I plan on:
The sticker sensors were cut open with a box cutter. They appear to contain a Freescale Accelerometer, a CC1101 transceiver,
and an unidentified μC. Interestingly, you can see handwritten designators near the antenna.
If you can identify either of the two non-CC1101 ICs please tweet @troylanes! Thanks!
UPDATE: The component on the right hand side is most likely a Freescale HCS08 MC9S08QA4CFQ. The accelerometer is probably a Freescale MMA8451Q or similar
After more "merciless probing" I think I've nailed the pinout for the Freescale μC. The only mystery is where does pin 8 go? With so few pins it's hard to believe there is a no-connect... There are some pads on the edge of the flex circuit that appear to be used for programming. I'm going to call it J1 (the silkscreen right below it is R2)
μC Pin | Connection(s) |
---|---|
1 NRST | J1 Pin 3 |
2 BKGD | J1 Pin 4, CC1101 CSN |
3 VDD | Battery + |
4 VSS | Battery - |
5 | Accelerometer SDA(**need to check datahseet) |
6 | CC1101 SI, CC1101 SO (via 47k resistor??) |
7 | CC1101 CLK, Accelerometer SCL (**need to check datasheet) |
8 | NC (really?) |
After a brief foray into trying to dump the firmware from the HCS08 I threw in the towel. I'll have a deeper look later, although, it appears that the chip is protected -- at least that's what my ebay debugger and codewarrior & friends tell me.
It was easy enough to tag into the empty solder pads near R5 and R8 to get access to the SPI lines going to the CC1101. My cheap hackaday parallax logic analyzer wasn't quite up to snuff for pulling down a decent sample to look at. The rigol scope + CSV dump + Excel, however, saved the day.
The scope dump was loaded into Excel, under wine, of course ;), a chart was made, and the ones and zeroes were bitbanged by my head. The first few registers and values gave me enough to find the rest in the hex dump from the egg. Enjoy -- Next up, to either dump firmware from the sensor and repurpose it, or, replace the μC with another.
Address | Register | Value |
---|---|---|
08 | PKTCTRL0 | 45 |
03 | FIFOTHR | 47 |
06 | PKTLEN | 3F |
0A | CHANNR | 00 |
2E | TEST0 | 09 |
24 | FSCAL2 | 2A |
25 | FSCAL1 | 00 |
26 | FSCAL0 | 1F |
22 | FREND0 | 10 |
29 | FSTEST | 59 |
0D | FREQ2 | 23 |
0E | FREQ1 | 31 |
0F | FREQ0 | 3B |
13 | MDMCFG1 | 22 |
14 | MDMCFG0 | F8 |
18 | MCSM0 | 18 |
0C | FSCTRL0 | 00 |
1D | AGCTRL0 | 91 |
Looks like the GG is using a fairly tuned CC1101 register set... -- 915 MHz, Channel 0, 200 kHz Channel spacing 10 kBaud data rate, 541.667 kHz RX Filter BW, No manchester, 2-FSK Modulation, 177.73 kHz Deviation, TX power 0 dBm, no PA ramping, with data Whitening.
My Rigol scope didn't have quite enough memory to do a decent job of capturing the complete config and packet sequence from the μC for use in my cheap Excel DLA. So, I rigged up a Ti LM3S1968 dev board with some simple SPI slave code that handled the rough start-up chatter on the clock line. The clock pulses are about a μs which does't allow for loose code. The Stellarisware is bloated enough, even running at 50 MHz, to blow chunks when trying to capture the data. Make sure to use the register keyword and not make calls like GPIOPinRead() -- read directly from the appropriate registers... Below is an OCR of the memory of my SPI buffer... this is the duct tape and bailing wire version.
made with gocr -- %s/l/1/g -- %s/J/7/g; -- %s/O/0/g; 30 0A 00 2E 09 24 2A 25 00 26 1F 22 10 29 59 0D 23 0E 31 0F 3B 13 22 14 F8 18 18 0C 00 1D 91 1B 43 23 E9 1C 40 21 56 19 16 2D 35 10 28 2C 81 11 93 12 03 15 66 0B 06 1A 6C 1E 08 1F 98 07 4C 16 08 17 03 20 78 02 2F 01 2E 00 06 08 45 03 47 06 3F 7E C0 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 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 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 00
I've heard of scope probes loading the probee, however, I've yet to experience it until now. After removing the scope probes I was able to see a packet get loaded into the TX FIFO and make it through to the egg and back to my server. See below.
Packet Send sequence: TX FIFO BURST ACCESS --> 7F LEN --> 07 DATA|40 00 00 21 4A 03 02| 35 <-- STX Packet logged in cgi: 47470024050200000050c2af12130000 <-- HEADER 07 DATA|40 00 00 21 4a 03 02| 59 fa <-- RSSI, LQI-- 000...
Next, to spoof packets from another CC1101 based device...
Sweet. It seems that the complete register map I posted (and retracted) was screwing something up. Simply writing the registers that are loaded by the sensors into my home grown CC1101 board and firing off packets worked (see below for byte array and code). I can now send arbitrary packets of up to 35 bytes in length. Trying to send larger packets results in what looks like a buffer overflow...
UNIXTIME EGG HEADER DATA LEN ARBITRARY DATA 1334197298 47470085050200000050cXXXXXXX0000 07 400000224b030254dd00000000000000000000000000000000000000000000000000000000 1334197906 474700c6050200000050cXXXXXXX0000 08 400000224b03020155cb000000000000000000000000000000000000000000000000000000 1334198190 474700e5050200000050cXXXXXXX0000 20 400000224b030201400000224b030201400000224b030201400000224b03020156d4000000 1334198221 474700e7050200000050cXXXXXXX0000 23 aa55cc11aa030201400000224b030201400000224b030201400000224b03020154454c56c4
void send_packet(){ unsigned char i = 0; const unsigned char gg_data[] = { 0x23, 0xAA, 0x55, 0xCC, 0x11, 0xAA, 0x03, 0x02, 0x01, 0x40, 0x00, 0x00, 0x22, 0x4B, 0x03, 0x02, 0x01, 0x40, 0x00, 0x00, 0x22, 0x4B, 0x03, 0x02, 0x01, 0x40, 0x00, 0x00, 0x22, 0x4B, 0x03, 0x02, 0x01, 'T', 'E', 'L' }; CC1101_strobe_register(SRES); //software reset CC1101_write_config(); //load up the config CC1101_strobe_register(IDLE); //go to idle mode CC1101_strobe_register(SFTX); //flush the TX FIFO CC1101_write_register_burst(0x7F, gg_data, gg_data[0] + 1); //load the TX FIFO, note that the first byte in burst mode to the TX fifo //must be the length of the data to send. the third paramter is the TOTAL //number of bytes to write after the register -- you get it... wait(5); //5 milliseconds CC1101_strobe_register(STX, NULL, 0); //command CC1101 to transmit wait(5000); //give the egg a rest... } void CC1101_write_config(){ int i = 0; //the array, below, are register value pairs, e.g. 0x0A is CHADDR and 0x00 is the value unsigned char const gg_config[] = { 0x0A, 0x00, 0x2E, 0x09, 0x24, 0x2A, 0x25, 0x00, 0x26, 0x1F, 0x22, 0x10, 0x29, 0x59, 0x0D, 0x23, 0x0E, 0x31, 0x0F, 0x3B, 0x13, 0x22, 0x14, 0xF8, 0x18, 0x18, 0x0C, 0x00, 0x1D, 0x91, 0x1B, 0x43, 0x23, 0xE9, 0x1C, 0x40, 0x21, 0x56, 0x19, 0x16, 0x2D, 0x35, 0x10, 0x28, 0x2C, 0x81, 0x11, 0x93, 0x12, 0x03, 0x15, 0x66, 0x0B, 0x06, 0x1A, 0x6C, 0x1E, 0x08, 0x1F, 0x98, 0x07, 0x4C, 0x16, 0x08, 0x17, 0x03, 0x20, 0x78, 0x02, 0x2F, 0x01, 0x2E, 0x00, 0x06, 0x08, 0x45, 0x03, 0x47, 0x06, 0x3F }; for (i = 0; i < sizeof(gg_config); i += 2){ CC1101_write_register(gg_config[i], gg_config[i + 1]); } }