Software-Design

Grundlagen der Sicherheit bei Embedded-Software

| Autor / Redakteur: David Kalinsky * / Franz Graser

Angreifer brauchen nicht immer ein externes Netzwerk

Der Angreifer könnte nun darauf setzen, dass Ihre Software die ADC-Daten in einem Stapelspeicher (Stack) sichert (vielleicht indem Sie alloca() oder malloca() verwenden). Wenn er Glück hat, könnte er einen Array-Überlauf verursachen, vielleicht indem er den Hardware-Timer manipuliert, der die ADC-Abfrage steuert.

Ein typischer Stack ist auf der Grafik auf dieser Seite abgebildet. Wenn der Angreifer einen Array-Überlauf verursacht, könnte der Stack korrumpiert werden, wie das Schema rechts daneben zeigt. Beachten Sie, dass die Rücksprungadresse im Stack an einer Position gespeichert wurde, die hinter dem Ende des Arrays lag.

Wenn der Angreifer die Korrumpierung richtig plant, wird der Überlauf den Ort im Stack erreichen, an dem die aktuelle Rücksprungadresse gespeichert wurde. Das kann dazu benutzt werden, um an diesem Platz im Stack einen Zeiger auf den eigenen Code einzuschleusen. Wenn Ihr Code die Rücksprungadresse benutzt, geht die Steuerung auf den Code des Angreifers über.

Das wird als „Stack Smashing“-Attacke bezeichnet. Beachten Sie, dass sie in diesem Beispiel völlig ohne Internetzugang und ohne Verbindung zu einer externen Datenleitung ausgeführt wurde.

Design-Grundlagen für sichere Embedded-Software

Beim Design eingebetteter Software können Entwickler die Sicherheit verbessern, indem sie einige Grundsätze beachten:

1. Misstrauische Aufteilung

Teilen Sie die Funktionalität Ihrer Software in einzelne Blöcke auf, die sich gegenseitig nicht vertrauen, um die Angriffsfenster in jedem Block zu verkleinern. In der Embedded-Software nennen wir diese Blöcke oft Prozesse oder Subsysteme oder CSCIs.

Entwerfen Sie jeden Block unter der Annahme, dass Softwareblöcke, mit denen er interagiert, angegriffen wurden, und dass in diesen interagierenden Blöcken Software des Angreifers anstatt der normalen Anwendung läuft. Vertrauen Sie den Ergebnissen der interagierenden Blöcke nicht. Geben Sie Ihre Daten nicht über gemeinsamen Speicher für andere Blöcke frei.

Benutzen Sie stattdessen ordnungsgemäße Mechanismen der Interprozesskommunikation wie Message Queues des Betriebssystems, Sockets oder TIPC (Transparent Inter Process Communication). Prüfen Sie die Inhalte, die Sie erhalten.

Als Resultat der misstrauischen Aufteilung fällt nicht das gesamte System in die Hände eines Angreifers, wenn ein einzelner Block kompromittiert wurde.

2. Trennung der Privilegien

Halten Sie den Teil Ihres Codes, der mit speziellen Rechten ausgestattet ist, möglichst klein. Wenn der Angreifer in Software einbricht, die mit hohen Privilegien ausgeführt wird, dann kann der Angreifer ebenfalls mit hohen Privilegien operieren. Das gibt ihm ein extrabreites Angriffsfenster in Ihr System.

Vermeiden Sie also, dass Anwendungssoftware im Kernel-Modus oder Master- Modus oder Supervisor-Modus läuft… oder wie das in Ihrer jeweiligen CPU-Architektur heißen mag. Überlassen Sie diesen Modus dem Betriebssystem. Lassen Sie Ihre Software ausschließlich im User-Modus laufen. Dadurch wird Ihre CPU-Hardware darauf verpflichtet, das Angriffsfenster in Ihrer Software zu limitieren.

3. Löschen Sie sensitive Informationen

Löschen Sie jede wieder verwendbare Ressource, wenn Sie sie freigeben! Wenn Sie eine Ressource freigegeben haben – sei es ein Speicherpuffer oder ein Software-Hardware-Interface/Datenregister, könnte der nächste Benutzer genau derselben Ressource ein Angreifer sein. Angreifer auf eingebettete Systeme lieben es, diese Ressourcen zu ‚phishen’, genau wie Internet-Angreifer gerne ‚phishen’. Sie wären bestimmt überglücklich, die Daten zu lesen, die Sie im Puffer bearbeitet haben oder die an die Hardware zur Ausgabe übergeben wurden!

Die meisten Dienste, die Ressourcen in Embedded-Umgebungen freigeben, markieren die Ressourcen einfach als „verfügbar“. Sie lassen die alte Information stehen, die in der Ressource enthalten ist. Damit ist sie potenziell für andere Benutzer lesbar. Das wird gemacht, da es sehr viel schneller geht, als die Ressource explizit auf Null zu setzen.

Wenn also die Applikation mit einer Ressource fertig ist, liegt es an der Anwendung, die Freigabe vorzubereiten, indem sie all diese Elemente auf Null setzt:

  • Heap-Puffer, Speicherpool-Puffer, Speicherpartitionssegmente,
  • Statisch zugewiesene Speicherpuffer,
  • Freigegebene Stack-Bereiche,
  • Speichercache,
  • Datei im Dateisystem,
  • Datenregister für Hardware-Interfaces, Statusregister, Kontrollregister.

Inhalt des Artikels:

Kommentar zu diesem Artikel abgeben
Sehr geehrter Herr Kielmann: ein paar Worte der Erläuterung: Ziel des Artikels war es, die...  lesen
posted am 01.06.2012 um 15:38 von Unregistriert

Sie schreiben...Der Angreifer könnte nun darauf setzen, dass Ihre Software die ADC-Daten in einem...  lesen
posted am 31.05.2012 um 11:41 von jkie


Mitdiskutieren
copyright

Dieser Beitrag ist urheberrechtlich geschützt. Sie wollen ihn für Ihre Zwecke verwenden? Infos finden Sie unter www.mycontentfactory.de (ID: 33374870 / Software-Entwurf & Echtzeit-Design)