The Crowdstrike Incident - Practical Part

From Embedded Lab Vienna for IoT & Security
Jump to navigation Jump to search

Summary

In this article, we present an experimental approach to investigating the mechanisms that led to the CrowdStrike Incident of July 2024. For a theoretical discussion of this event, please refer to the corresponding entry in the Elvis Science Wiki: https://wiki.elvis.science/index.php?title=The_Crowdstrike_Incident

Preamble

The Falcon software itself is only distributed to corporate customers, as it is purely an enterprise product. Therefore, experimenting with the Falcon Sensor for private or student purposes is not possible. However, we will practically demonstrate the mechanisms that led to the system failure. We will address the following points:

  • Out-of-bounds memory access
  • Race Condition

Requirements

For experimenting on a safe environment, we will setup a virtual machine.

  • Operating system: MacOS Sequoia 15.1.1
  • Virtualization: UTM; https://mac.getutm.app/
  • Virtualized system: Windows 11, ARM-version (compatible for Apple Silicon)
  • for coding in C: CLion as IDE
  • for coding in Java: IntelliJ as IDE

Description

Demonstrating Out-Of-Bounds Memory Access

For the practical demonstration, a virtual environment is set up using Windows 11 via UTM on macOS as the host operating system, ARM variant. This allows for experimentation independent of the host system. CLion, an IDE for C, is installed on the virtual machine. The reason for using C as the programming language lies in its memory management - here, faulty memory access can be simulated very easily. We write a program that is supposed to store data passed into an array. An object is defined using TYPEDEF and later instantiated in the running program, which receives more data to write than memory space allocated. This subsequently leads to a malfunction in memory management. In the case of the incident in question, an out-of-bounds write error occurred.

Code-Example

#include <string.h>                                                                                                    
#include <stdio.h>                                                                                                     
                                                                                                                       
// Definition der Struktur mit typedef                                                                                 
                                                                                                                       
typedef struct {                                                                                                       
    char data[10]; // Ein kleines Array, um Overflows zu demonstrieren                                                 
} Object;                                                                                                              
                                                                                                                       
void demonstrate_out_of_bounds_read(Object *obj) {                                                                     
    printf("Out-of-Bounds Read:\n");                                                                                   
    for (int i = 0; i < 15; i++) { // Gezieltes Überschreiten der Grenze                                               
        printf("data[%d] = %c (Adresse: %p)\n", i, obj->data[i], &obj->data[i]);                                       
    }                                                                                                                  
}                                                                                                                      
                                                                                                                       
void demonstrate_out_of_bounds_write(Object *obj) {                                                                    
    printf("Out-of-Bounds Write:\n");                                                                                  
    for (int i = 0; i < 15; i++) { // Gezieltes Schreiben über die Grenze hinaus                                       
        obj->data[i] = 'A' + i; // Zufällige Daten schreiben                                                           
        printf("data[%d] = %c (Adresse: %p)\n", i, obj->data[i], &obj->data[i]);                                       
    }                                                                                                                  
}                                                                                                                      
                                                                                                                       
int main() {                                                                                                           
    Object obj;                                                                                                        
                                                                                                                       
    // Daten initialisieren                                                                                            
    strncpy(obj.data, "123456789", sizeof(obj.data) - 1);                                                              
    obj.data[sizeof(obj.data) - 1] = '\0'; // Null-Terminierung                                                        
                                                                                                                       
   printf("Initialisiertes Objekt: %s\n\n", obj.data);                                                                 
                                                                                                                       
   // Demonstration                                                                                                    
   demonstrate_out_of_bounds_read(&obj);                                                                               
   printf("\n");                                                                                                       
   demonstrate_out_of_bounds_write(&obj);                                                                              
                                                                                                                       
   return 0;                                                                                                           
}

Output on Terminal

C:\Users\test\CLionProject\out_of_bound\cmake-build-debug\
out_of_bound.exe
Initialisiertes Objekt: 123456789
Out-of-Bounds Read:
data[0] = 1 (Adresse: 00000004A99CF958)
data[1] = 2 (Adresse: 00000004A99CF959)
data[2] = 3 (Adresse: 00000004A99CF95A)
data[3] = 4 (Adresse: 00000004A99CF95B)
data[4] = 5 (Adresse: 00000004A99CF95C)
data[5] = 6 (Adresse: 00000004A99CF95D)
data[6] = 7 (Adresse: 00000004A99CF95E)
data[7] = 8 (Adresse: 00000004A99CF95F)
data[8] = 9 (Adresse: 00000004A99CF960)
data[9] = (Adresse: 00000004A99CF961)
data[10] = (Adresse: 00000004A99CF962)
data[11] = (Adresse: 00000004A99CF963)
data[12] = (Adresse: 00000004A99CF964)
data[13] = (Adresse: 00000004A99CF965)
data[14] = (Adresse: 00000004A99CF966)

Out-of-Bounds Write:
data[0] = A (Adresse: 00000004A99CF958)
data[1] = B (Adresse: 00000004A99CF959)
data[2] = C (Adresse: 00000004A99CF95A)
data[3] = D (Adresse: 00000004A99CF95B)
data[4] = E (Adresse: 00000004A99CF95C)
data[5] = F (Adresse: 00000004A99CF95D)
data[6] = G (Adresse: 00000004A99CF95E)
data[7] = H (Adresse: 00000004A99CF95F)
data[8] = I (Adresse: 00000004A99CF960)
data[9] = J (Adresse: 00000004A99CF961)
data[10] = K (Adresse: 00000004A99CF962)
data[11] = L (Adresse: 00000004A99CF963)
data[12] = M (Adresse: 00000004A99CF964)
data[13] = N (Adresse: 00000004A99CF965)
data[14] = O (Adresse: 00000004A99CF966)
Process finished with exit code 3


Error message when executing the demo code
Out-of-bounds read [01]
Out-of-bounds write [01]

Demonstrating Race Condition

For this demonstration, we use the Java programming language, as it allows us to demonstrate a race condition through multithreading tasks by adding or removing the “synchronized” keyword. The goal is to manipulate a value. Access to the variable will be accomplished through multiple threads. Since we will omit synchronization in the experiment, a race condition will occur. The expected value will not match the actual value of the variable.

Code-Example with Race Condition

public class RaceConditionDemo {
    private static int counter = 0; // Gemeinsame Ressource
    public static void main(String[] args) {
        // Erstellt zwei Threads, die die gleiche Aufgabe ausführen
        Thread thread1 = new Thread(new IncrementTask());
        Thread thread2 = new Thread(new IncrementTask());

        System.out.println("Initialer Zählerwert: " + counter);

        // Startet beide Threads
        thread1.start();
        thread2.start();

        try {
            // Wartet, bis beide Threads fertig sind
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Endgültiger Zählerwert: " + counter);
        System.out.println("Erwarteter Zählerwert: " + IncrementTask.NUM_ITERATIONS * 2);
    }

    // Aufgabe, die von beiden Threads ausgeführt wird
    static class IncrementTask implements Runnable {
        static final int NUM_ITERATIONS = 1000000;

        @Override
        public void run() {
            for (int i = 0; i < NUM_ITERATIONS; i++) {
                counter++; // Unsynchronisierter Zugriff auf die gemeinsame Ressource
            }
        }
    }
}

Terminal-Output with Race Condition

"C:\Program Files\Eclipse Adoptium\jdk-17.0.13.11-hotspot\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJIDEA 2024.2.4\lib\idea_rt.jar=49872:C:\Program Files\JetBrains\IntelliJ IDEA 2024.2.4\bin" -Dfile.encoding=UTF-8 -classpath C:\projekt\race_condition\out\production\race_condition RaceConditionDemo
Initialer Z hlerwert: 0
Endg ltiger Z hlerwert: 1810187
Erwarteter Z hlerwert: 2000000
Process finished with exit code -1073741819 (0xC0000005)

corrected Code-Example without Race Condition

...
@Override
 public void run() {
  for (int i = 0; i < NUM_ITERATIONS; i++) {
   synchronized (RaceConditionDemo.class) { //Synchronisation auf der gesamten Ressource
   counter++; // Synchronisierter Zugriff auf die gemeinsame Ressource
   }
  }
 }
...

Terminal-Output without Race Condition

"C:\Program Files\Eclipse Adoptium\jdk-17.0.13.11-hotspot\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJIDEA 2024.2.4\lib\idea_rt.jar=49872:C:\Program Files\JetBrains\IntelliJ IDEA 2024.2.4\bin" -Dfile.encoding=UTF-8 -classpath C:\projekt\race_condition\out\production\
race_condition RaceConditionDemo
Initialer Z hlerwert: 0
Endg ltiger Z hlerwert: 2000000
Erwarteter Z hlerwert: 2000000

References

[01] https://www.zimperium.com/blog/cve-2018-9411-new-critical-vulnerability-multiple-high-privileged-android-services/