Introduction
In this post, I'll discuss using power fault injection (glitching) to bypass UART password authentication in an application running on a simple Arduino dev board using the Bus Pirate. (Spoiler - it works!)
The Bus Pirate is an open source hardware/firmware debugging and test tool that is capable of many, many things useful to an embedded engineer and/or hacker. In this case, we'll be using the UART functionality to communicate with and time, generate, and inject a power fault into our target (an Arduino Uno).
Background
Bus Pirate Code and Custom Glitching Circuit
- Send an '\r' (return) character from the Bus Pirate over UART to the target
- Wait a precise amount of time after the UART line goes back high from step 1
- Turn on one of the outputs from the Bus Pirate for a very precise and short amount of time
- Read the response from the target over UART - analyze that response to see if it looks different than the normal "### Please enter password ###" prompt. If the response is different, stop and announce that the glitch was successful; otherwise, jump back to step 1.
The Target
As noted earlier, the target is an Arduino Uno development board. This target was chosen because of low cost and wide availability. I had two Sparkfun RedBoards in my parts drawer to use.
This board uses the Atmel ATMega32p microcontroller - an 8-bit RISC Harvard architecture chip with 32K of internal flash and 2K of internal SRAM running at 16MHz. Programming can be done with the Arduino IDE and downloaded using USB.
To begin with, the schematic was reference to find a place to make a connection on the power circuit to inject the fault:
For best results, the point to do this should be very physically near the chip, so the usual place to connect to "short" the incoming power is across a decoupling capacitor near the microcontroller itself. According to the schematic, the only decoupling capacitor appears to be C2, and it is not near the chip.
The 5VDC supply to the chip (VCC) comes in on pins 4 and 6, with pins 3 and 5 being ground. Short wires were soldered on to pins 3 and 4 which then connected to an SMA coax connector:
The SMA connector was affixed to the board with hot-melt glue. The SMA connector was used so a short coax cable could connect the target to the glitching hardware - this reduces unwanted parasitic capacitance or inductance in that connection.
Target Firmware
As stated earlier in this post, I was working on a consumer IoT device that had custom password verification added to U-Boot. I eventually extracted the bootloader and reversed engineered that password code.
That password verification code was included in the target's Arduino sketch:
int simpleCLILoop(const char* prompt) {char password[MAX_PWD_LEN] = {'\0'};uint8_t pwdInx;int readChar;int compareResult;// if password has been entered (consoleEnabled == true), then// skip this whole block. Otherwise keep trying until correct// password has been entered.while (!consoleEnabled) {Serial.println("### Please enter password ###\r\n"); // prompt userpwdInx = 0; // entered password character counter// loop runs continuously until <RETURN> key pressedwhile (true) {if (Serial.available() > 0) { // wait for UART rxreadChar = Serial.read(); // get that charreadChar &= 0xff; // mask off lower 8 bitsif (readChar == '\r') { // user hit <RETURN>break; // so break out of forever while() loop} else if (readChar == '\b') { // user hit <BACKSPACE>if (pwdInx > 0) { // if at least one char has been entered--pwdInx; // decrement entered password char countSerial.print("\b \b"); // send backspace, space, backspace over UART} // to clear the last '*'} else { // not <RETURN> or <BACKSPACE>password[pwdInx] = readChar; // append the this char to the user's password entry++pwdInx; // increment the entered password char countSerial.print('*'); // print out an asterix} // testing entered UART char} // waiting for UART char} // main while() looppassword[pwdInx] = '\0'; // null terminate user entered password// compare user entry to "good" passwordcompareResult = strcmp(PASSWORD, password);if (!compareResult) {consoleEnabled = true; // set bit to indicate password is good!}Serial.println(""); // print out a newline} // while (!consoleEnabled)*consoleBuffer = '\0';return (readCommandFromUART(prompt));}
(Yes, I know there's a stack-based buffer overflow here, and that it's a bit sketchy in general, but this is the code I pulled from a real-world consumer IoT device. The only difference between the consumer device and this is that I'm being lazy and using the Arduino "Serial" class instead of directly reading/writing to the ATMega 328's "UDR0" register.)
The code to target here is:
If we can inject a fault at the right time, the code could skip over the if() statement and assign a "true" to the global "consoleEnabled" variable.
For even more detail, the disassembly from the region near the password check looks like this:
code:02f5 0e 94 be 03 call strcmp
code:02f7 89 2b or R24,R25
code:02f8 11 f4 brbc LAB_code_02fb,Zflg
code:02f9 a0 92 d4 02 sts consoleEnabled,R10
LAB_code_02fb XREF[1]: code:02f8(j)
code:02fb 85 ea ldi R24,0xa5
code:02fc 91 e0 ldi R25,0x1
This line:
code:02f5 0e 94 be 03 call strcmp
calls strcmp(), the result is returned as a 16-bit integer in registers R24 and R25. The next line:
code:02f7 89 2b or R24,R25
this is a branch instruction which will branch to the label LAB_code_02fb if the Z flag in the status register is cleared. In other words, jump to that label if the password was incorrect. If the Z flag was clear (strcmp() returned non-zero), the code will skip this next line:
code:02f9 a0 92 d4 02 sts consoleEnabled,R10
which sets the global "consoleEnabled" to be "true". (The compiler reused register R10 from some previous thing here.)
Looking at that, the specific instruction to "glitch" or skip over would be the brbc branching instruction. It's also possible to inject a fault into the code of the strcmp() routine so it returns zero, but this is where I am targeting.
Connecting Things Up
Here is the BP with glitching plank connected to the Arduino.
In the upper right are the UART TX and RX signals from the Arduino going to the BP. Next is the coax cable connecting the two together; the +5VDC (VCC) right at the AVR chip across the FET. The two wires from the lower header on the target connect the Arduino's 5V and gnd to the glitching plank. The BP is using the target's voltage to power the I/O buffers.
Finding the Right Time
As described above, the key is finding the right time to inject the fault. I used a logic analyzer to help figure this out. The analyzer was connected to the TX and RX lines and recorded the sequence of transmit from the BP and receive from the Arduino:
Performing the Fault Injection Attack
Now is the time to actually do the attack! First, connect to the Bus Pirate's serial console and enable UART mode. Once that's done, I issued the "bridge" command and powered up the Arduino:
(The Bus Pirate powers up in a safe High Impedance mode; the "m 3" command is shorthand for mode 3 - UART). The bridge command sets the BP's UART into "transparent bridge", and shows the target is in password mode.
Bridge command was exited with the BP's button and the "glitch" command was issued:
Breaking all of that down, first the glitch configuration information is shows (and can be edited):
- Glitch trigger character: The ASCII value of what to send to trigger the whole sequence. In this case, it's ASCII 13, a RETURN character.
- Glitch trigger delay: How long after sending the trigger character to delay before turning on the output that fires the FET? This value is in terms of nanoseconds * 10; so a value of 14 is 14,000 nanoseconds, or 14 microseconds. That puts it in the window shown on the logic analyzer
- Glitch vary time: This is an additive time to vary the glitching timing, again in nanoseconds * 10. In this case, it's a 5; the actual glitches will be fired at 14.00us, 14.01us, 14.02us, 14.03us, 14.04us, and 14.05us.
- Glitch output on time: How long to keep the FET gate turned on. This should be a short time; too long and you can trip the brown out detect and reset the device, too short won't trigger a glitch
- Glitch cycle delay: Minimum time between attempts; this is used to keep the FET or other hardware from overheating
- Normal response character: The ASCII value of something returned by the target when operating normally. In this case, it's an ASCII 80, which is a capital 'P'. If the glitch did not trigger the bypass, the target will respond with "### Please enter password ###". Therefore, if a capital "P" was RX'd by the UART, the code knows it didn't succeed
- Number of glitch attempts: The number of cycles to attempt before giving up. A cycle is a series of attempts including all variations based on the Glitch vary time.
- Bypass 'READY' input checking: The glitching code is generic enough to be used in different ways; for example, to trigger an external device that may need to recharge between attempts. To accommodate that, the glitching firmware can wait until an input has been turned on by that external device. In this case, the FET-based plank doesn't need this, so it is bypassed.
Conclusion
References
- Bus Pirate website: https://buspirate.com/
- Bus Pirate users forum: https://forum.buspirate.com/
- ATMega328p datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
- AVR Instruction set: https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf
- Sparkfun RedBoard schematic: https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/RedBoard-V22.pdf
No comments:
Post a Comment