Here is the final part of my NDH 2011 Badge Hacking serie.
We know how to plug it, read it ... how about we write in it now?
Pre-requisites
As you could see from the first post about the pinout, there are PORTA, PORTB and PORTD.
These are defines in AVR C headers that allows you to set those corresponding ports.
Setting one of the bits of a PORT would set the corresponding PIN to high or 1. Basically if you set bit 6 (we are counting from 0), you will switch on LED 5.
On tixlegeek's blog, you should also have seen DDRB and DDRD. These sets the pins as inputs or outputs. Setting a bit to 1 set the corresponding pin as an output and 0 as an input.
LED stand for "Light Emiting Diode" for those who do not know. And Diodes are component that allow the current to flow in only one direction (not speaking about the Zener diode though).
And last thing but not the least, do not forget that we are coding on a micro-controller so we have a limited amount of space (either in RAM, ROM, etc), limited amount of processing power, well limited resources.
Taking that into account, you will see some "ugly hacks" to go around problems such as RAM exhaustion like you will see in my example program.
So what can you do with such a small micro-controller?
To be simple, a micro-controller is a chip with very limited resources or a component integrating multiple functionnalities in one chip: video, audio, CPU, image processing, etc.
A micro-controller can be used as a control device, smalls robotics or mapping LEDs to boards, etc.
In this case, the badge only have 7 LEDs for outputs and not inputs but the programming of the badge.
You are only limited by your creativity (and resources).
You could do those for example:
- small animations from right to left, left to right, etc
- counting
- sending data (you would need a receiver though)
- showing a dump from a network capture for example
- etc
Yeah pretty much anything you can represent with 7 LEDs, 7 bits, etc.
But to tell the truth, yeah you can't code much stuffs on it :p.
I decided to code a morse code emitter :).
But first, let's see the original code.
Original firmware
Here what the original firmware is:
//B1 B0 D6 D5 D4 D3 D2 /* ** Compiler Include Directives */ #define F_CPU 8000000 #include <avr/io.h> #include <util/delay.h> void copy2array(char value); int main(void) { char EasterMSG[]="Nothing there N00b!"; char CHALL[]={0x62,0x4f,0x46,0x46,0x45,0xa,0x7d,0x45,0x58,0x46,0x4e,0xa,0x65,0x4c,0xa,0x64,0x6e,0x62,0x18,0x61,0x1b,0x1b, 0x00}, *challptr=CHALL, i=0; PORTB=EasterMSG[72]; DDRD=0xff; DDRB=0xff; while(1==1) { i=0; while(*(CHALL+i)) { copy2array(*(CHALL+i)); _delay_ms(4000); i++; } } } void copy2array(char value) { char byte=0; PORTB=0; PORTD=0; PORTB |= (((value^42)&_BV(0))?_BV(1):0); PORTB |= (((value^42)&_BV(1))?_BV(0):0); PORTD |= (((value^42)&_BV(2))?_BV(6):0); PORTD |= (((value^42)&_BV(3))?_BV(5):0); PORTD |= (((value^42)&_BV(4))?_BV(4):0); PORTD |= (((value^42)&_BV(5))?_BV(3):0); PORTD |= (((value^42)&_BV(6))?_BV(2):0); }
For the most attentive people, you must have spotted the buggy code:
void copy2array(char value) { char byte=0; PORTB=0; PORTD=0; PORTB |= (((value^42)&_BV(0))?_BV(1):0); PORTB |= (((value^42)&_BV(1))?_BV(0):0); PORTD |= (((value^42)&_BV(2))?_BV(6):0); PORTD |= (((value^42)&_BV(3))?_BV(5):0); PORTD |= (((value^42)&_BV(4))?_BV(4):0); PORTD |= (((value^42)&_BV(5))?_BV(3):0); PORTD |= (((value^42)&_BV(6))?_BV(2):0); }
If you recall the full pinout:
------------------------- | PORTD | ------------------------- | PIND0 | RX | | PIND1 | TX | | PIND2 | D4 | | PIND3 | D3 | | PIND4 | D2 | | PIND5 | D1 | | PIND6 | D5 | ------------------------- | PORTB | ------------------------- | PINB0 | D6 | | PINB1 | D7 | | PINB2 | NC | | PINB3 | NC | | PINB4 | NC | | PINB5 | MOSI | | PINB6 | MISO | | PINB7 | SCK | ------------------------- | PORTA | ------------------------- | PINA0 | NC | | PINA1 | NC | | PINA2 | RESET | -------------------------
Then after fixing the code you get this:
void copy2array(char value) { char byte=0; PORTB=0; PORTD=0; PORTD |= (((value^42)&_BV(0))?_BV(5):0); PORTD |= (((value^42)&_BV(1))?_BV(4):0); PORTD |= (((value^42)&_BV(2))?_BV(3):0); PORTD |= (((value^42)&_BV(3))?_BV(2):0); PORTD |= (((value^42)&_BV(4))?_BV(6):0); PORTB |= (((value^42)&_BV(5))?_BV(0):0); PORTB |= (((value^42)&_BV(6))?_BV(1):0); }
Ok now, let's go on with Morse code :).
Let's code: Morse code
Here is my "firmware" to do morse code:
// @author : m_101 // @license : beerware // @year : 2011 // @program : Do morse code on leds of NDH 2011 badge // standard libraries #include <ctype.h> #include <string.h> // avr specific libraries #define F_CPU 1000000 #include <avr/io.h> #include <avr/pgmspace.h> #include <util/delay.h> // defines for turning on a single led at a time #define D1_ON() PORTD |= _BV(5) #define D2_ON() PORTD |= _BV(4) #define D3_ON() PORTD |= _BV(3) #define D4_ON() PORTD |= _BV(2) #define D5_ON() PORTD |= _BV(6) #define D6_ON() PORTB |= _BV(0) #define D7_ON() PORTB |= _BV(1) // defines for turning off a single led at a time #define D1_OFF() PORTD &= ~_BV(5) #define D2_OFF() PORTD &= ~_BV(4) #define D3_OFF() PORTD &= ~_BV(3) #define D4_OFF() PORTD &= ~_BV(2) #define D5_OFF() PORTD &= ~_BV(6) #define D6_OFF() PORTB &= ~_BV(0) #define D7_OFF() PORTB &= ~_BV(1) // morse code duration (international standard) #define DOT_DURATION 200 #define DASH_DURATION 3*DOT_DURATION #define INTERGAP_DURATION DOT_DURATION #define GAP_LETTERS 3*DOT_DURATION #define GAP_WORDS 7*DOT_DURATION void leds_morse(char value); // turn off the leds #define leds_off() \ PORTB = 0; \ PORTD = 0 // put morse table in program space (not enough RAM) // international morse code // letters char morse_A[] PROGMEM = ".-"; char morse_B[] PROGMEM = "-..."; char morse_C[] PROGMEM = "-.-."; char morse_D[] PROGMEM = "-.."; char morse_E[] PROGMEM = "."; char morse_F[] PROGMEM = "..-."; char morse_G[] PROGMEM = "--."; char morse_H[] PROGMEM = "...."; char morse_I[] PROGMEM = ".."; char morse_J[] PROGMEM = ".---"; char morse_K[] PROGMEM = "-.-"; char morse_L[] PROGMEM = ".-.."; char morse_M[] PROGMEM = "--"; char morse_N[] PROGMEM = "-."; char morse_O[] PROGMEM = "---"; char morse_P[] PROGMEM = ".--."; char morse_Q[] PROGMEM = "--.-"; char morse_R[] PROGMEM = ".-."; char morse_S[] PROGMEM = "..."; char morse_T[] PROGMEM = "-"; char morse_U[] PROGMEM = "..-"; char morse_V[] PROGMEM = "...-"; char morse_W[] PROGMEM = ".--"; char morse_X[] PROGMEM = "-..-"; char morse_Y[] PROGMEM = "-.--"; char morse_Z[] PROGMEM = "--.."; // digits char morse_0[] PROGMEM = "-----"; char morse_1[] PROGMEM = ".----"; char morse_2[] PROGMEM = "..---"; char morse_3[] PROGMEM = "...--"; char morse_4[] PROGMEM = "....-"; char morse_5[] PROGMEM = "....."; char morse_6[] PROGMEM = "-...."; char morse_7[] PROGMEM = "--..."; char morse_8[] PROGMEM = "---.."; char morse_9[] PROGMEM = "----."; // conversion table PGM_P code[] PROGMEM = { // A ... M morse_A, morse_B, morse_C, morse_D, morse_E, morse_F, morse_G, morse_H, morse_I, morse_J, morse_K, morse_L, morse_M, // N ... Z morse_N, morse_O, morse_P, morse_Q, morse_R, morse_S, morse_T, morse_U, morse_V, morse_W, morse_X, morse_Y, morse_Z, // 0 .. 9 morse_0, morse_1, morse_2, morse_3, morse_4, morse_5, morse_6, morse_7, morse_8, morse_9 }; // get index in morse code table int tomorse_idx (char value) { int idx = -1; if (isalpha(value)) idx = (toupper(value) - 'A') % 26; else if (isdigit(value)) idx = (value - '0') % 10 + 26; return idx; } int main (void) { // message to show and its index char idxMsg; char msg[] = "Hello World For NDH 2011"; // morse code and its index char idxMorse; char morse[8] = {0}; // index in morse conversion table char idxCode; // init port B and D data direction as outputs DDRD = 0xff; DDRB = 0xff; // init PORTS PORTB = 0; PORTD = 0; // repeat message while(1) { idxMsg = 0; // print string while not ended while(*(msg+idxMsg)) { idxMorse = 0; // morse code index idxCode = tomorse_idx(*(msg+idxMsg)); // ensure cleaning of the local buffer memset(morse, 0, sizeof(morse)); // put code in RAM if got a correct idx if (idxCode >= 0 && idxCode < 36) strcpy_P(morse, (PGM_P)pgm_read_word(&(code[idxCode]))); // parse morse code while(*(morse+idxMorse)) { // led show morse code leds_morse(*(morse+idxMorse)); // inter-gap between dots and dashes leds_off(); _delay_ms(INTERGAP_DURATION); // next morse symbol idxMorse++; } // blank leds_off(); // gap between letters if (isalnum(*(msg+idxMsg))) _delay_ms(GAP_LETTERS); // gap between words else _delay_ms(GAP_WORDS); idxMsg++; } } } // turn on leds for morse code void leds_morse(char value) { // init PORTS PORTB = 0; PORTD = 0; // "long press" if (value == '-') { D1_ON(); D2_ON(); D3_ON(); D4_ON(); D5_ON(); D6_ON(); D7_ON(); _delay_ms(DASH_DURATION); } // "short press" else if (value == '.') { D3_ON(); D4_ON(); D5_ON(); _delay_ms(DOT_DURATION); } }
The AVR chip is set to work at 1Mhz :).
I guess it is a bit more readable concerning switching ON or OFF a specific LED.
You must have seen the weird way of creating the string table and using it. We basically only have 128 bytes of RAM, the string tables would thus obviously not fit in it completely (with other local variables in a function). We thus force the compiler to put our string table in program space and copy the corresponding morse sequence to a local buffer before using it.
The code is well commented (too much commented? :)) so you would not have any problems reading it. If you were to spot any bugs, do not hesitate to send me a patch ;).
As an exercise to the reader, I let you modify the code so that it directly turns on the LED given a morse code sequence.
Here is a small utility to convert an ASCII string to morse code:
// @author : m_101 // @license : beerware // @year : 2011 // @program : Convert ASCII string to Morse code sequence #include <stdio.h> #include <stdlib.h> char* tomorse (char value) { char *code[] = { // A ... M ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", // N ... Z "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", // 0 .. 9 "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----." }; int idx; if (isalpha(value)) { idx = (toupper(value) - 'A') % 26; return code[idx]; } else if (isdigit(value)) { idx = (value - '0') % 10 + 26; return code[idx]; } return " "; } int main (int argc, char *argv[]) { if (argc < 2) { printf("Usage: %s str\n", argv[0]); return 0; } while (*argv[1]) { printf("%s ", tomorse(*argv[1])); argv[1]++; } putchar('\n'); return 0; }
Play with it,
$ avr-gcc -Os -g -Wall -I. -mmcu=attiny2313 -c -o badge_morse.o badge_morse.c badge_morse.c: In function 'main': badge_morse.c:145: warning: array subscript has type 'char' $ avr-gcc -g -mmcu=attiny2313 -o badge_morse.elf badge_morse.o $ avr-objcopy -j .text -j .data -O ihex badge_morse.elf badge_morse.hex $ sudo avrdude -c usbasp -p attiny2313 -U flash:w:badge_morse.hex -v avrdude: Version 5.10, compiled on Jun 29 2010 at 21:09:48 Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/ Copyright (c) 2007-2009 Joerg Wunsch System wide configuration file is "/etc/avrdude.conf" User configuration file is "/home/kurapix/.avrduderc" User configuration file does not exist or is not a regular file, skipping Using Port : /dev/parport0 Using Programmer : usbasp AVR Part : ATtiny2313 Chip Erase delay : 9000 us PAGEL : PD4 BS2 : PD6 RESET disposition : possible i/o RETRY pulse : SCK serial program mode : yes parallel program mode : yes Timeout : 200 StabDelay : 100 CmdexeDelay : 25 SyncLoops : 32 ByteDelay : 0 PollIndex : 3 PollValue : 0x53 Memory Detail : Block Poll Page Polled Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack ----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- --------- eeprom 65 6 4 0 no 128 4 0 4000 4500 0xff 0xff flash 65 6 32 0 yes 2048 32 64 4500 4500 0xff 0xff signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00 lock 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00 lfuse 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00 hfuse 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00 efuse 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00 calibration 0 0 0 0 no 2 0 0 0 0 0x00 0x00 Programmer Type : usbasp Description : USBasp, http://www.fischl.de/usbasp/ avrdude: auto set sck period (because given equals null) avrdude: warning: cannot set sck period. please check for usbasp firmware update. avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.01s avrdude: Device signature = 0x1e910a avrdude: safemode: lfuse reads as 64 avrdude: safemode: hfuse reads as DF avrdude: safemode: efuse reads as FF avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: auto set sck period (because given equals null) avrdude: warning: cannot set sck period. please check for usbasp firmware update. avrdude: reading input file "badge_morse.hex" avrdude: input file badge_morse.hex auto detected as Intel Hex avrdude: writing flash (898 bytes): Writing | ################################################## | 100% 0.63s avrdude: 898 bytes of flash written avrdude: verifying flash memory against badge_morse.hex: avrdude: load data flash data from input file badge_morse.hex: avrdude: input file badge_morse.hex auto detected as Intel Hex avrdude: input file badge_morse.hex contains 898 bytes avrdude: reading on-chip flash data: Reading | ################################################## | 100% 0.48s avrdude: verifying ... avrdude: 898 bytes of flash verified avrdude: safemode: lfuse reads as 64 avrdude: safemode: hfuse reads as DF avrdude: safemode: efuse reads as FF avrdude: safemode: Fuses OK avrdude done. Thank you.
Conclusion
As you could see, with little imagination and work, you can achieve interesting and fun stuffs. We now know how to plug it, read it, write in/program it, hell yeah we mastered it ;).
Just a last message for those who got a NDH Badge 2011: Do you know that not all speakers/challengers/etc got to have one (I got really lucky, I almost did not get one)? By the way, a looot of people will not even play with it. Damn, if you take it, play with it! It is not just to look pretty (people put work into making them).
Hope you enjoyed it,
Have fun,
Cheers,
m_101
Resources:
- Morse code
- [NDH2K11] Badges hackable!
- NDH2K11's Badge: Spec. & hackz
- NDH2K11's Badge: PROGRAMMATIONNNNNN!!!!
- Manual of avrdude
- AVR 8-bit Instruction Set
- AVR Programming
- AVR GCC Tutorial (1) – Basic I/O Operations
- AVR : Tutorial 2 : AVR – Input / Output
- AVR GCC FAQ
- Program Space
Aucun commentaire :
Enregistrer un commentaire