![]() |
|
Serviervorschläge:
|
![]() |
OWL-Transmission (1) - Timing-Trick
Problem: Der Bootloader auf einem Mikrocontroller kann keinen ununterbrochenen Datenstrom verarbeiten. Das hat technische Gründe und trifft für AVR wie für die meisten Controller-Architekturen zu, die internen nichtflüchtigen Speicher überschreiben können (Self-programming). Eine übliche Bootloader-Firmware muss die empfangenen Daten auf Fehlerfreiheit prüfen, und wenn noch Entschlüsselung dazu kommt, kostet das richtig viel Rechenzeit. Der eigentliche Knackpunkt sind aber die physischen Schreibzugriffe in EEPROM oder Flash. Dafür wird der Prozessorkern tatsächlich angehalten, sodass auch eine ausgeklügelte Interruptsteuerung keinen kontinuierlichen Datenempfang sicherstellen kann. Durch die physischen Schreibzugriffe entsteht immer eine Zwangspause (wir nennen sie mal reißerisch "Totzeit"), in der unsere Bootloader-Firmware nichts mehr mitbekommt. |
Das OWL-Signal ist eine einseitige RS232-Übertragung im Modus 8-N-1 in der gegebenen Baudrate. Das OWL-Signal lässt sich über jede auch nur halbwegs normgerechte RS232-Schnittstelle senden.
|
![]() Zeitverlauf einer OWL-Transmission für ATtiny25 mit 10 MHz (4 Blöcke EEPROM, 8 Blöcke Flash bei 9600 Baud)
|
Allgemeines: Das obenstehende Diagramm wurde vom Mitschnitt einer realen OWL-Transmission nach dem ausgearbeiteten Verfahren abgeleitet. Es handelt sich um einen durchgehenden Sendedatenstrom, aber die verschiedenen Datentypen sind grafisch hervorgehoben. Wir sehen auf einen Blick, dass die Strecken mit Füllzeichen (PREAMBLE) ziemlich unterschiedliche Längen haben. In einer bidirektionalen Übertragung müssten an diesen Stellen Protokollzeichen ausgetauscht und Sendepausen eingehalten werden, abgesehen davon würde sich aber bezüglich der Nutzdaten ein ähnliches Muster ergeben. Die individuell berechneten Präambeln einer OWL-Transmission berücksichtigen die Totzeiten (Datenverarbeitung plus Schreibzugriffe) im Ziel-Controller abhängig vom momentanen Betriebszustand und ersetzen das bidirektionale Protokoll. Block-Sendedauer: Der einzelne Datenblock besteht immer aus einem Startzeichen und 16 verschlüsselten Bytes. Die Sendedauer eines Datenblocks ist somit bei gegebener Baudrate immer gleich. Bei 9600 Baud dauert jeder Block etwa 18 Millisekunden (tB). Einleitende Präambel (INTRO PREAMBLE): Zum Beginn einer OWL-Sitzung muss sich der Empfänger erstmalig auf das ankommende Sendesignal einstellen oder in eine bereits laufende Präambel einsteigen. Die INTRO PREAMBLE ist in der Beispielgrafik recht kurz dargestellt. Sie kann beliebig verlängert werden. Das ist hilfreich, wenn Controller-Reset und Sendebeginn manuell koordiniert werden müssen. Block-Entschlüsselungsdauer: Die Entschlüsselung eines Datenblocks kostet Rechenzeit. Bei Anwendung einer ernsthaften Chiffre und Taktfrequenzen im einstelligen MHz-Bereich sind das schon einige Millisekunden. Jeder Block muss entschlüsselt werden. Deshalb fließt die "decryption time" (tD) in alle Präambel-Berechnungen mit ein und gibt also die Mindestdauer der Präambeln vor. Sicherheitshalber legt man noch ein paar Zeichen "obendrauf", um Laufzeitabweichungen auszugleichen und um dem Empfänger eine sichere Nachsynchronisation zu ermöglichen. Im Vergleich zur Entschlüsselung brauchen die Schreibzugriffe in EEPROM und Flash aber zum Teil noch viel länger. Authentisierungs-Sequenz (S1): Die ersten drei aufeinanderfolgenden Blöcke dienen der Authentisierung des Senders gegenüber dem Empfänger, also der "Anmeldung" beim Bootloader. Dieser Schritt spielt sich ausschließlich im Arbeitsspeicher des Controllers ab und bedeutet kaum zusätzlichen Rechenaufwand. Daher reichen zwischen den Blöcken der Authentisierungs-Sequenz die Basis-Präambeln mit der Mindestlänge (tD) aus. EEPROM-Sequenz (S2): EEPROM-Speicher kann direkt überschrieben werden, braucht aber pro Byte schon etliche Millisekunden, sodass sich die Schreibzeit für einen Block aus 16 EEPROM-Datenbytes auf satte 60 Millisekunden (tEW) summiert. Da müssen wir durch, aber nur, wenn tatsächlich EEPROM-Daten geschrieben werden. Die längeren Präambeln nach jedem EEPROM-Datenblock sind in der Grafik deutlich zu erkennen. Flash-Sequenz (S3): Nach dem ersten Flash-Datenblock zeigt das Diagramm eine auffällig lange Pause von ca. 180 ms (tFE). In dieser Phase löscht der Bootloader den gesamten für Applikations-Firmware reservierten Flash, bevor neue Daten geschrieben werden können. Danach geht es zügig voran mit einzelnen Flash-Seitenzugriffen. Dem aufmerksamen Leser wird nicht entgangen sein, dass die Länge der Präambeln zwischen den Flash-Datenblöcken leicht variiert. Das hängt mit der Speicherorganisation im Flash zusammen. Im Ziel-Chip (hier ATtiny25), ist der Flash-Memory in Pages (Speicherseiten) von je 32 Bytes unterteilt. Der Datenblock hat aber immer genau 16 Bytes. Also muss der Bootloader immer die Daten von 2 Blöcken zusammenfassen, bevor er eine ganze Flash-Page schreiben kann. Dementsprechend muss der Sender auch nur nach jedem zweiten Block die verlängerte Präambel für den Flash-Write-Zyklus (tFW) in den Datenstrom einfügen. (Anmerkungen: Auf Controllern mit größerem Flash ist gewöhnlich auch die Pagesize größer. Dann findet der Flash-Write nur noch alle 4, 8 oder 16 Blöcke statt, und es ergibt sich ein beachtlicher Optimierungseffekt. (Auf ATmegas kann das vorherige Löschen und auch eine etwaige Not-Löschung des AppFlash komplett entfallen, weil man sich hier nicht vom Bootloader "aussperren" würde, falls fehlerhafter Code stehen bleibt. Weiteres dazu in der Beschreibung der Firmware.) Ausleitende Präambel (OUTRO PREAMBLE): In manchen Fällen ist es aus technischen Gründen notwendig, die Übertragung nach dem letzten Datenblock nicht abrupt enden zu lassen. Dann hängt man eben noch eine "ausleitende Präambel" hinten dran. Sie dient nur dazu, den Übertragungskanal noch für eine definierte Zeit offenzuhalten. Zu den Berechnungen: Um das maßgeschneiderte Timing zu erzeugen, braucht der Sender ein Vorwissen über den Empfänger. Der Sender muss die Speicherorganisation, die durchschnittliche Anzahl von Prozessortakten bei der Block-Entschlüsselung und natürlich die Taktfrequenz des Ziel-Controllers kennen. Wie diese Informationen verwaltet und verarbeitet werden, steht unter Software. |
Hörbeispiel:
OWL-Transmission mit 1 kB Nutzdaten bei 9600 Baud als Audiodatei Hinweis: Das Hörbeispiel ist eine Vertonung des seriellen Datensignals, die den zeitlichen Charakter einer OWL-Transmission verdeutlichen soll. Es ist nicht dasselbe, war aber Ideengeber für das später entwickelte und weiter unten erwähnte Exportformat 'OWL-Audio'! |
OWL-Transmission (2) - Synchronisation und AutobaudingDer Sender fügt zwischen den Datenblöcken eine genau berechnete Anzahl von Präambel-Zeichen ein und realisiert damit garantierte Schutzintervalle, die dem Empfänger genügend Zeit zur Verarbeitung der bisherigen Daten verschaffen.In dem Moment, wo sich der Empfänger wieder auf die Leitung schaltet, trifft er auf die letzten Zeichen der auslaufenden Präambel. Diese ermöglichen dem Empfänger eine schnelle Neusynchronisation auf den seriellen Zeichenrahmen und einen präzisen Abgleich auf die serielle Geschwindigkeit (Autobauding). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() Auswertung der OWL-Präambel vor dem Wiedereinstieg in die Datenübertragung
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Festlegungen:
Erste Synchronisation:
Synchro-Autobaud-Messung:
Datenempfang:
Anmerkungen:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Hinweis: RS232-Signalverläufe sind hier als Logiksignale mit unipolaren Pegeln in Normallage dargestellt, wie sie von der U(S)ART-Komponente des Mikrocontrollers erwartet werden und üblicherweise an TTL-kompatiblen Ausgängen eines MAX232 oder FT232 vorliegen. Hier ist die logische "1" (Stoppbit oder Leerlauf) durch einen High-Pegel (meist 3,3 oder 5 Volt) definiert, eine logische "0" entspricht dem Low-Pegel (0 Volt). Darüber hinaus kann die OWL-Firmware auch für Empfangssignale in Inverslage mit umgedrehten Logikpegeln konfiguriert werden, siehe weiter unten. Dies bringt in einigen Fällen weitere Hardware-Vereinfachungen. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PerformanceNachstehende Tabelle gibt einen Überblick, was mit dem Synchro-Autobaud-Verfahren im OWL-Bootloader machbar ist. In einem realen Testaufbau mit ATtiny2313-20PU an 5V mit dem klassischen MAX232-Pegelwandler habe ich Reihenversuche bei verschiedenen Controller-Taktraten durchgeführt. Die Test-Transmission enthielt jeweils ein Datenmuster von 2 x 64 Bytes zum Füllen des EEPROM sowie ein einfaches LED-Blinkprogramm für den Flash, das mit ca. 1 kB an Zufallsdaten aufgefüllt war.Der Chip wurde vor jedem neuen Versuch über ISP komplett gelöscht, um "falsch positive" Ergebnisse auszuschließen. Auswertung: Wenn die LED unmittelbar nach Abschluss der Transmission fröhlich zu blinken anfing, dann muss logischerweise die gesamte Transmission bei der verwendeten Baudrate vollständig und fehlerfrei durchgelaufen sein. Es bestätigte sich, dass die Fehlererkennung durch die aktuelle Bootloader-Firmware extrem zuverlässig funktioniert. (Siehe OWL-Signal - Logisches Format.) Baudraten-Fenster bei verschiedenen Prozessor-Taktraten
Anmerkungen:
|
OWL-Transmission (3) - Logisches FormatDie Präambeln realisieren auf der Transportschicht ein maßgeschneidertes Timing, das den Rückkanal zur Datenflusssteuerung überflüssig macht und dem Empfänger immer wieder ein Referenzsignal für Synchronisation und Autobauding liefert.Für das logische Format einer OWL-Transmission spielen die technischen Signaleigenschaften keine Rolle. Auf der Kryptoschicht wird nur mit den Datenblöcken zu 16 Bytes gearbeitet. Alle Blöcke sind mit RST verschlüsselt. Aufgrund des verwendeten Feedback-Modus' dieser Blockchiffre ist jeder einzelne Block von allen vorausgegangenen Blöcken kryptografisch abhängig. Diese Eigenschaft lässt sich zur sicheren Fehlererkennung nutzen. Der rechtmäßige Empfänger kann zweifelsfrei feststellen, ob alle Daten in einem logischen Abschnitt ("RST-Sequenz") komplett und korrekt übertragen wurden und bekommt zusätzlich das Signal, dass die nächste Sequenz beginnt, denn ... Die OWL-Transmission besteht aus drei verketteten RST-Sequenzen, die immer in derselben Reihenfolge gesendet werden: Authentisierung, EEPROM-Daten, Flash-Daten. Beim Wechsel zur nächsten Sequenz wird der Schlüsselgenerator jedoch nicht wieder mit dem Initialschlüssel zurückgesetzt, sondern läuft weiter. Damit sind nicht nur die Blöcke innerhalb einer Sequenz, sondern alle 3 Sequenzen kryptografisch voneinander abhängig. Wird der abschließende VI-Block erkannt, weiß der Empfänger, dass die vorausgegangenen Daten fehlerfrei und vollständig übertragen wurden. Anschließend erwartet er die Daten der Folgesequenz. Im Fehlerfall verbleibt der Empfänger in der gegenwärtigen Sequenz und kann am Ende der Transmission Maßnahmen zur Schadensbegrenzung treffen. Eine weiter gehende Differenzierung ist für den Einweg-Bootloader auch gar nicht nötig. Logischer Ablauf der OWL-Transmission:
Normalfall: Der Sender verwendet denselben Schlüssel, wie der Empfänger. Die Übertragung kommt von Anfang bis Ende störungsfrei durch. Der Empfänger kann jeden einzelnen Block korrekt entschlüsseln, erkennt die VIs der jeweiligen Sequenzen und erkennt auch den abschließenden VI der letzten Sequenz S3. Damit ist sicher, dass alle bereits geschriebenen Daten vollständig und fehlerfrei durchgekommen sind. Die Sitzung war erfolgreich und die Bootloader-Firmware übergibt die Kontrolle an eine bestehende oder neu geschriebene Applikations-Firmware. Indirekte Rückmeldung: Sofortiger Start der Applikation. Ausnahmefälle: Übertragungsfehler; Unterbrechungen, zu lange Sendepausen (Timeout); fehlgeschlagene Nachsynchronisation bei stark gestörtem Signal; versehentliche Anwendung eines falschen Schlüssels; absichtliche Anwendung eines falschen Schlüssels zur Adressierung eines anderen Empfängers auf derselben Leitung; plumpe Manipulationsversuche. Alle diese Situationen erkennt der Empfänger zuverlässig als FEHLER. Je nachdem, in welcher Sequenz er sich gerade befindet, trifft der Empfänger Gegenmaßnahmen. Indirekte Rückmeldung: Blockade bis zum Hardware-Reset. Weitere Überlegungen: Die OWL-Transmission kommt, abgesehen von Präambel und Blockstarter, ohne weitere Steuerzeichen und Kommandos aus. Innerhalb der verschlüsselten Blöcke werden keine sonstigen Metadaten übertragen, die einen Known-Plaintext-Angriff begünstigen würden. Im Gegenteil, das Chiffrat bekommt mit jedem neuen IV zusätzliche Entropie verpasst. Obwohl es sich um ein recht starres Schema handelt, sind die Schreibzugriffe in EEPROM oder Flash optional. Es ist selbstverständlich möglich, nur in den EEPROM oder nur in den Flash zu schreiben, ohne jeweils den anderen Speicherbereich zu berühren. Es ist ebenso möglich, ein komplettes Firmware-Update, bestehend aus Daten für EEPROM und Flash, in einer und derselben Transmission bzw. in einer Transmissions-Datei unterzubringen. |
KryptografieDer OWL nutzt eine Blockchiffre mit der Bezeichnung RST ("Randomized Substitutions and Transpositions"). Dabei handelt es sich um ein ehemaliges Studienprojekt zum Thema Blockverschlüsselung. Mein inzwischen gut erprobtes Kryptosystem ist auf praktische Anwendbarkeit und Transparenz angelegt. Es definiert nicht nur eine kombinatorisch eher simple Blockchiffre, sondern auch einen dazu passenden Key-Feedback-Modus und ein bevorzugtes Dateiformat.Doch warum in diesen Zeiten "das Rad neu erfinden"? Unbenommen, auf der PC-Plattform gibt es eine Menge an Krypto-Optionen, die brauchbar erscheinen und dem weit verbreiteten Glauben an "Zertifiziertes" huldigen. Zu einigen Algorithmen gibt es sehr schnelle Umsetzungen in Software und auch Hardware (z. B. AES), sodass diese in der Regel einen höheren Datendurchsatz erzielen, als meine inoffizielle und bisher nur teiloptimierte PC-Implementierung von RST. Bei Mikrocontroller-Anwendungen sind die Prioritäten etwas anders gelagert. So zeigte sich im Vergleich mit prominenten Kandidaten, dass RST als Bootloader-Chiffre einen attraktiven Kompromiss aus Codeeffizienz und Sicherheit zu bieten hat. Funktionsweise von RSTDas Verfahren nutzt für seine "randomisierten Substitutionen und Transpositionen" einfachste arithmetische Standardoperationen, wie Addition, Bitshifting, Invertierung und Vertauschung, auf denen auch etablierte Blockchiffren beruhen. Die Rechenschritte innerhalb einer Blockrunde werden bei RST jedoch nicht über magische Konstanten oder Tabellen gesteuert, sondern von einem fortlaufenden Pseudozufallsgenerator, der lediglich zum Beginn der Ver- oder Entschlüsselung mit dem geheimen Schlüssel als Startzahl (seed) geladen wird.Der PRNG ist also wortwörtlich eine "Schlüsselkomponente" des Kryptosystems. Er kann nach unterschiedlichen Qualitätskriterien realisiert werden. So lässt sich je nach Anwendungsgebiet ein günstiger Kompromiss aus Rundenzahl, PRNG-Stärke und Codebedarf (Mikrocontroller-Anwendungen) finden. Der Block-Algorithmus von RST erzielt schon bei Mindestrundenzahl eine ausgezeichnete Balancierung der Bit-Häufigkeiten und einen mittleren bis guten Avalanche-Effekt. Letzteres bedeutet, ein einziges "gekipptes" Bit im Chiffrat verändert nach der Entschlüsselung ca. 25 bis 50 Prozent der Bits im wiederhergestellten Klartextblock. Nach jeder vollständigen Blockrunde wird der innere Zustand des PRNG mit dem Klartext des vorherigen Blockes modifiziert. Daraus resultiert eine massive Fehlerfortpflanzung, die Grundlage für einen zuverlässigen Authentisierungs- und Fehlererkennungsmechanismus.
DateiformatFür PC-Anwendungen entstand ein einfaches Format zur authentisierbaren und kryptografisch abgesicherten Übertragung von Dateien. Das RST-Kryptogramm besteht ausschließlich aus verschlüsselten Blöcken (kein Header) und erfüllt auch sonst alle statistischen Anforderungen an ein gutes Chiffrat. Es ist von einer Reihe echter Zufallszahlen nicht unterscheidbar und selbstverständlich erzeugt derselbe Klartext aufgrund der randomisierenden Komponente jedes Mal ein komplett anderes Chiffrat. Nur die erfolgreich entschlüsselte RST-Sequenz gliedert sich in drei Abschnitte:Der erste Block ist der einzige Block, der mit dem Originalschlüssel verschlüsselt wird. Dieser erste Block ist mit Zufallszahlen gefüllt. Infolge der Klartext-Schlüssel-Rückkopplung verändert dieser Zufallsblock nach der ersten Runde den Zustand des PRNG. Somit hat der erste Block einer RST-Sequenz die klassische Funktion eines Initialisierungsvektors oder IV. Durch diese Randomisierung werden die nachfolgenden Blöcke mit einem komplett neuen Zufallsschlüssel verarbeitet. Damit sind triviale Angriffe auf das Kryptosystem (v. a. "known plaintext") ziemlich sicher ausgeschlossen, da es, vereinfacht gesagt, für verschlüsselte Zufallszahlen keine kryptoanalytische "Abkürzung" gibt. Der IV-Block hat noch eine weitere Funktion. Sein Muster aus Zufallszahlen wird mit allergrößter Wahrscheinlichkeit in der gesamten Nachricht kein zweites Mal vorkommen. Das bedeutet, der IV kann als eindeutige Markierung für das Nachrichten-Ende benutzt werden. Zur funktionalen Unterscheidung nenne ich diese Wiederholung des IV-Musters "VI". Zum Beginn der Entschlüsselung einer RST-Sequenz speichert der Empfänger einfach den ersten entschlüsselten Block ab und vergleicht diesen VI mit jedem nachfolgend entschlüsselten Block. Dabei wendet er ein paar einfache Regeln an:
"OWL-RST"
Für den One-Way-Loader kommt eine modifizierte 128-Bit-Variante von RST zum Einsatz. Block- und Schlüsselweite sind beim One-Way-Loader auf 128 Bits (16 Bytes) festgelegt.Das OWL-Kryptogramm besteht immer aus drei aneinandergereihten kompletten RST-Sequenzen. Diese Sequenzen sind zwar in sich abgeschlossen, aber wie die einzelnen Blöcke kryptografisch voneinander abhängig, denn der PRNG wird im Laufe der Bootloader-Sitzung nicht mehr zurückgesetzt. Somit liefert der letzte VI eine Über-Alles-Prüfsumme zur Gesamtübertragung. Die Fähigkeit, einen Fehler in der Gesamtübertragung sicher zu erkennen, ist für einen Einweg-Bootloader äußerst wertvoll. Als PRNG kommt die Software-Umsetzung eines klassischen Galois-LFSR zum Einsatz, welches um die mächtige Self-Shrinking-Generatorfunktion erweitert wurde. Diese sorgt für eine deutliche kryptografische Abhärtung der LFSR-Sequenz (Eliminierung von Autokorrelation) und bringt im Zusammenhang mit dem Key-Feedback ein hohes Maß an Nichtlinearität in das Kryptosystem ein. Für die Implementierung auf dem Mikrocontroller ist entscheidend, dass sich LFSR-basierte PRNGs recht codeeffizient, elegant und transparent in Assembler umsetzen lassen. Da RST keine Feistel-Chiffre ist, müssen die Pseudozufallsvektoren für Verschlüsselung und Entschlüsselung jeweils in umgekehrter Reihenfolge zur Anwendung kommen. Daraus folgert, dass entweder beim Verschlüsseln oder beim Entschlüsseln die ganze Pseudozufallssequenz für eine komplette Blockrunde zwischengespeichert werden muss. Auf der Gegenseite können die Zahlen dann aber direkt aus dem PRNG gezogen werden. Es versteht sich von selbst, dass in der Bootloader-Anwendung die speicher- und codesparende Variante auf den Mikrocontroller verlagert wird. Zur DiskussionFolgende Vorteile sehe ich in der Verwendung von "OWL-RST": Der PRNG läuft immer weiter. Jeder Krypto-Block wird mit einem komplett neuen Schlüsselsatz beackert. Eine auffällige Musterbildung, wie sie bei klassischen Blockchiffren in einem unglücklich gewählten Feedback-Modus vorkommt, ist prinzipbedingt ausgeschlossen. Hier profitiert die Blockchiffre von den Eigenschaften einer Stromchiffre.Der Verzicht auf Lookup-Tabellen und komplizierte Schlüssel-Transformationen spart bei der Implementierung auf Mikrocontrollern eine Menge Speicherplatz (siehe Firmware). Schon bei Mindestrundenzahl erreicht RST recht ordentliche Balancierungs-, Konfusions- und Diffusions-Eigenschaften. Zusätzliche Maßnahmen zum Whitening des Chiffrats sind überflüssig. Die Klartext-Schlüssel-Rückkopplung von RST ermöglicht die Verwendung eines geheimen IV und garantiert massive Fehlerfortpflanzung, was wiederum die Grundlage für die sichere Über-Alles-Fehlererkennung ist. Offensichtlicher Nachteil: Der PRNG läuft immer weiter... Ein kryptografisch starker PRNG (Cryptographically Strong PRNG, auch "CSPRNG") braucht Rechenzeit. Auf dem PC war RST128 in einer teiloptimierten Variante, die noch vergleichsweise holprig in FreeBASIC geschrieben war, um das 3- bis 5-fache langsamer, als eine vergleichbare Implementierung von AES. Mit TEA/XTEA lag sie fast gleichauf. (Die Benchmarks werde ich wohl mal mit einer in C neugeschriebenen Referenzimplementierung wiederholen müssen.) Mein vorläufiges Fazit: In der Bootloader-Umgebung auf einem Mikrocontroller spielt die Performance der Krypto-Schicht eine untergeordnete Rolle. Der Flaschenhals sind hier immer die physischen Schreibzugriffe und die serielle Übertragung. Mit OWL-RST steht eine echte Blockchiffre zur Verfügung, die mit sehr kompaktem Code solide Verschlüsselungssicherheit einschließlich Fehlererkennung und Authentisierung bietet. Kritik an dieser hausgemachten Lösung ist billig und legitim. In diesem Zusammenhang möchte ich darauf hinweisen, dass die wohl kritischste Komponente, der Pseudozufallsgenerator, eher konservativ ausgewählt wurde. Es handelt sich um ein 128-Bit-LFSR mit einfachem Generatorpolynom (Galois-Feedback auf den Bits 128, 127, 126 und 121), welches durch die Self-Shrinking-Filterfunktion bedeutend abgehärtet wurde. Diese Variante gilt als kryptografisch sicher. Weiter gehe ich davon aus, dass die nachgeschaltete Blockchiffre und der IV-VI-Mechanismus keine neuen Schwachstellen aufreißen, sondern im Gegenteil sämtliche Angriffe, die auf Extraktion und Analyse der LFSR-Sequenz abzielen, noch erheblich erschweren. Hierzu höre ich gern qualifizierte Argumente und Vorschläge. Nach oben | Index |
FirmwareDie OWL-Firmware ist natürlich in AVR-Assembler geschrieben. Sie nutzt den Standard-Befehlssatz, welcher von nahezu allen 8-Bit-AVRs unterstützt wird. Die Firmware macht sich von keinen spezifischen Hardwarekomponenten abhängig und verwendet keine Interrupts. Der Quellcode ist dank bedingter Assemblierung auf mehr als 120 verschiedene ATtinys und ATmegas anwendbar. Mit den neuesten Anpassungen in der Syntax ist der Assemblercode vollständig kompatibel zu Atmel's proprietärem Assembler, aber auch zur freien und plattformübergreifend verfügbaren Alternative gavrasm! Wie bereits im Abschnitt 'Software' erwähnt, ist ein Assemblieren von Bootloader-Firmware normalerweise nicht nötig. Es steht Maschinencode für alle unterstützten AVR-Modelle zur Verfügung, der in einem automatisierten Verfahren nur noch an Benutzervorgaben angepasst werden muss. Die Programme für den PC sind alle in Standard-C geschrieben, restlos offengelegt, wohlstrukturiert und reichlich kommentiert.Features:
Es folgt: High-Level-Beschreibung zu den wesentlichen Funktionen der OWL-Firmware Aufruf: Gewöhnlich erfolgt der Aufruf eines Bootloaders über einen Hardware-Reset. Dieser wird durch eine steigende Flanke auf dem Controller-Eingang RESET, durch Verbinden des Controllers mit der Stromversorgung, durch Abfall und Wiederanstieg der Versorgungsspannung oder durch den Watchdog-Timer ausgelöst. Der Aufruf des Bootloaders per Hardware-Reset ist die technisch sauberste und rechtlich einwandfreie Methode, um eine klare Trennung zwischen Bootloader- und Applikations-Firmware zu gewährleisten. Die neue Firmware von 2020 kann diese Reset-Quellen unterscheiden und entweder darauf reagieren, also den Bootloader-Betrieb starten, oder aber direkt zur Applikations-Firmware weiterleiten. (Siehe Erläuterungen zum Feature "Reset Sources"!) Auch Grandioses lässt sich mitunter noch verbessern... Seit April 2023 verfügt die Firmware über ein weiteres nützliches Feature in Bezug auf das Reset-Verhalten des OWL. Eine Applikation, die ihrerseits den Bootloader aufrufen will, kann durch komplettes Löschen des Controller-Statusregisters und anschließenden unbedingten Sprung zur Bootloader-Startadresse die Reset-Quellenprüfung umgehen und den Bootloader auf elegante Weise aufrufen. (Details weiter unten: "Reset-Filter-Umgehung") Initialisierung: Der Bootloader wird nach dem Hardware-Reset als erstes Programm gestartet. Er initialisiert den Stackpointer und alle von ihm selbst genutzten Register, Ports und Speicherzellen. Es findet keine Initialisierung des übrigen SRAM oder der sonstigen I/O-Ports statt.
Synchro-Autobauding: Der Bootloader wartet bis zum Timeout darauf, dass ein Signal auf der für den Datenempfang konfigurierten Leitung eintrifft. Ein erster Pegelwechsel wird grundsätzlich verworfen, um ein "Einpendeln" des Signals zu ermöglichen. Nachfolgende Pegelwechsel werden mit dem oben beschriebenen Autobaud-Verfahren ausgewertet. Sofern das ankommende Signal eine OWL-Präambel ist und hinreichend sauber durchkommt, wird die Erstsynchronisation gelingen. Damit besitzt der Bootloader eine Timing-Referenz für den Empfang des ersten Datenblocks mittels Software-Decoder. Der Synchro-Autobaud-Zyklus wird mit jeder Präambel wiederholt. Dadurch ist dieser Bootloader völlig immun gegen mittel- bis langfristige Schwankungen der Taktfrequenzen auf Sender- und Empfängerseite. Auch sehr lange Durchgänge sind trotz driftender Taktfrequenzen kein Problem. Blockdatenempfang: Nach erfolgreichem Synchro-Autobauding werden nachfolgende Zeichen über die reguläre Empfangsroutine decodiert. Zunächst wartet eine Schleife die restlichen Präambel-Zeichen ab, bis das Blockstart-Zeichen erkannt wurde. Die darauf folgenden 16 Bytes sind der verschlüsselte Datenblock. Sie werden zur weiteren Verarbeitung in einen SRAM-Puffer übernommen. Block-Daten entschlüsseln: Der Bootloader klinkt sich aus der seriellen Übertragung aus und beginnt mit der Entschlüsselung des im SRAM-Puffer vorliegenden Datenblocks. Für 4 Runden auf 16 Bytes werden insgesamt 64 Pseudozufallsvektoren à 4 Bit aus dem PRNG gezogen. Die vom PRNG gelieferten Pseudozufallsvektoren hängen vom inneren Zustand des PRNG ab, welcher in den Registern r0-r15 gespeichert ist. Dieser ändert sich durch Anforderung weiterer Zufallsbits fortlaufend. Nach dem Entschlüsseln liegen die 16 (mutmaßlich) fehlerfreien Bytes im selben Puffer vor. Der frisch entschlüsselte Block wird per XOR auf die PRNG-Register r0-r15 rückgekoppelt. Dadurch fließt Entropie aus den Nutzdaten in die nachfolgende Ver-/Entschlüsselung ein. Vor allem wird eine massive Fehlerfortpflanzung erreicht, die Grundlage für den leistungsfähigen Authentisierungs- und Fehlererkennungsmechanismus. Der erste Block einer neu begonnenen Sequenz, der IV-Block, besteht aus Zufallszahlen, die nach der Entschlüsselung auf den PRNG-Zustand rückgekoppelt werden. Faktisch läuft der PRNG also ab dem zweiten Block einer Sequenz mit einem völlig neuen Zufallsschlüssel weiter. Außerdem wird der IV-Block als "VI" in einen zweiten SRAM-Puffer kopiert. Alle danach entschlüsselten Blöcke werden im Laufe der RST-Sequenz mit dem VI verglichen. Solange der aktuelle Block nicht mit dem VI identisch ist, geht das Programm davon aus, dass es sich um reguläre Schreibdaten handelt, die entsprechend weiterverarbeitet werden. Erkennt das Programm, dass der aktuelle Block mit dem VI identisch ist, dann wurde die RST-Sequenz erfolgreich abgeschlossen und der Bootloader weiß, dass alle vorherigen Blöcke in Ordnung waren. Gegebenenfalls schaltet er nun zur nächsten Sequenz weiter. Im Fehlerfall wird der VI niemals erkannt und das Programm verbleibt in der aktuellen Sequenz, bis die Übertragung zuende geht und es zum Timeout kommt. Weiteres unter "Fehlerbehandlung". Die gesamte Blockentschlüsselung benötigt in Assembler einschließlich PRNG gerade einmal 50 Maschinenbefehle! Blockvergleich und Key-Feedback konnten in einer gemeinsamen Schleife untergebracht werden, die gerade einmal 12 Maschinenbefehle braucht. Authentisierungs-Sequenz (S1): Bevor der Bootloader irgendwelche Schreibzugriffe zulässt, muss er sichergehen, dass der Sender das Schlüsselgeheimnis kennt. Ein "Türsteher" muss halt sein, damit nicht sonstwer reinkommt, einem die Bude vollkotzt und den Hund vergewaltigt. Die Authentisierung gegenüber dem Bootloader ist natürlich keine simple Zugangssperre im Sinne einer Passwortabfrage. Die Authentisierung erfolgt über eine RST-Sequenz, die keine Nutzdaten enthält, sondern genau einen Block mit Dummy-Daten und einen abschließenden VI. Nur, wenn dieser VI als gültig erkannt wurde, war die Authentisierung erfolgreich. In diesem Fall geht der Bootloader vertrauensvoll in die nächste Runde, die EEPROM-Sequenz S2. In allen anderen Fällen, also bei Verwendung eines falschen Schlüssels oder bei gestörter Übertragung, wird die Authentisierungs-Sequenz S1 nicht korrekt abgeschlossen. Der Bootloader verharrt in einer Endlos-Schleife ohne Timeout. Dieser Zustand kann nur noch über einen erneuten Hardware-Reset beendet werden. Der Blockade-Mechanismus erlaubt es, mehrere Bootloader mit verschiedenen Schlüsseln und sogar mit unterschiedlichen technischen Voraussetzungen, was die möglichen Baudraten angeht, auf einer gemeinsamen Programmierleitung anzusprechen. Pro Reset-Zyklus wird dann immer nur der tatsächlich angesprochene Bootloader die für ihn bestimmte Übertragung auswerten. Alle nicht angesprochenen Bootloader machen nach dem dritten Block "dicht", weil sie ihn nicht als VI erkennen, und verhalten sich bis zum nächsten Hardware-Reset passiv, d. h. eine bereits vorhandene Applikations-Firmware wird nicht angetastet und nicht unkontrolliert gestartet. Wegen der Schlüsselverkettung ist die OWL-Transmission fälschungssicher in dem Sinne, dass ein Angreifer, der lediglich eine gültige Transmission aus S1 oder S1+S2 vor eine eigene Übertragung kopiert, auf diese Weise keine sinnvollen Änderungen in EEPROM oder Flash schreiben kann. Bei einer wie auch immer manipulierten Übertragung wird der abschließende VI von S3 niemals erkannt. Der Bootloader kann am Ende der Übertragung Gegenmaßnahmen treffen, mindestens aber in den Blockadezustand gehen. EEPROM-Sequenz (S2): Der Bootloader entschlüsselt und kopiert den IV der EEPROM-Sequenz. Die nachfolgenden Datenblöcke werden in Schüben von 16 Bytes im Atomic-Write-Modus in den EEPROM geschrieben. Enthielt die EEPROM-Sequenz keinen einzigen Datenblock (nur IV und VI), dann wird im EEPROM auch nichts verändert und der Bootloader geht über zur Flash-Sequenz S3. Für den EEPROM erfolgt kein separater Löschzyklus. Das bedeutet, Speicherinhalte, die sich im oberen EEPROM-Adressbereich befinden, werden nicht angetastet, wenn ein neuer Schreibzugriff nur tiefer liegende Adressen berührt. Das kann für inkrementelle EEPROM-Updates ausgenutzt werden. Ein separater EEPROM-Löschmodus erscheint verzichtbar. Zum Löschen des gesamten EEPROM wird eine Sequenz übertragen, die den ganzen Speicherbereich abdeckt und mit $00 oder $FF überschreibt. In der EEPROM-Sequenz müssen keine Maßnahmen gegen Adressüberlauf getroffen werden. Sollte der Bootloader das Ende der EEPROM-Sequenz wegen Übertragungsfehlern nicht mitkriegen, so würden die weiteren Schreibzugriffe einfach nur immer wieder den EEPROM überschreiben. Dieses Risiko erscheint tragbar. Der EEPROM ist gewissermaßen die Knautschzone vor dem kritischen Flash-Zugriff. Kommt es in der EEPROM-Sequenz zum Timeout, dann geht der Bootloader in den Blockadezustand, sodass auch hier eine indirekte Rückmeldung über den Misserfolg der Sitzung erfolgt. Wurde die EEPROM-Sequenz korrekt mit dem VI abgeschlossen, dann erwartet der Bootloader als Nächstes die Flash-Sequenz S3. Flash-Sequenz (S3): Der Bootloader entschlüsselt und kopiert den IV der Flash-Sequenz. Wenn der darauf folgende Block bereits der VI ist, dann ist der Bootloader schon fertig und wird den Flash unangetastet lassen. Kommen nach dem IV jedoch Flash-Schreibdaten, dann löscht der Bootloader zunächst den gesamten Applikations-Flash unterhalb der Bootloader-Firmware. Der Löschzyklus erfolgt auf ATtinys mit Rücksicht auf die besonderen Risiken top-to-bottom. Generell muss der Bootloader Flash-Daten, die ja immer in Blöcken von 16 Bytes eintreffen, bis zur tatsächlichen Seitengröße (PAGESIZE) zusammenfassen, bevor er sie in einem Rutsch in die aktuelle Page schreiben kann. Durch die Teilung in 16-Byte-Einheiten ist die Flash-Schreibroutine recht zukunftssicher. Sie würde im Prinzip noch bis zu einer Pagesize von 4096 Bytes funktionieren... (Das entspricht der Blockgröße bei heutigen SSD-Speichern. Die größte auf ATmegas eingesetzte Pagesize beträgt 256 Bytes.) Auf ein explizites Verifying der geschriebenen Daten wird jetzt komplett verzichtet. Die langjährigen Erfahrungen mit TSB haben gezeigt, dass es NIE zu fehlerhaften Flash-Schreibzugriffen kommt, sobald die Daten erst einmal fehlerfrei im Schreibpuffer gelandet sind und für die Dauer des Flash-Write einigermaßen stabile Betriebsbedingungen geherrscht haben. Beim OWL entschärft sich die Situation noch, da der gesamte Übertragungsweg durch das Kryptosystem abgesichert ist. Wurde der letzte VI aus S3 erkannt, dann war die Transmission im Ganzen erfolgreich. Bereits geschriebene Daten sind mit allergrößter Wahrscheinlichkeit fehlerfrei und dürfen in den jeweiligen Speicherbereichen stehen bleiben. In diesem Fall ist der Bootloader auch schon fast am Ende. Wurde kein VI erkannt, dann hat der Bootloader mit dem Ablauf des Timeout die schreckliche Gewissheit, dass in der Flash-Sequenz etwas schief gelaufen ist. Jetzt wird der Bootloader konsequenterweise den Applikations-Flash wieder löschen, damit hier kein fehlerhafter Code stehen bleibt. Erfolgreiche Transmission und Übergabe an Applikations-Firmware: Nach erfolgreichem Abschluss der Flash-Sequenz S3 übergibt der Bootloader praktisch verzögerungsfrei an die neu geschriebene oder an eine unverändert gelassene Applikations-Firmware. Auf einem ATtiny muss der Bootloader noch einmal nach dem INFO TAG suchen, welcher mit einer neuen Firmware ebenfalls neu geschrieben wurde. Dort steht dann der aktuelle Reset-Vektor der Applikations-Firmware als absolute Adresse. Diese springt der Bootloader per indirektem Jumpbefehl an und startet somit die Applikations-Firmware. Auf einem ATmega muss der Bootloader nur den Zugriff auf den RWW-Speicherbereich (= Applikations-Flash) freigeben. Dann springt er pauschal nach $0000, wo sich üblicherweise die Interrupt-Tabelle mit dem unveränderten Original-Reset-Vektor der Applikation befindet, und die Applikation startet. Günstig ist es also, wenn sich die Applikation irgendwie bemerkbar machen kann, also zum Beispiel mit ihrem Programmstart eine LED zum Leuchten bringt, eine Display-Meldung ausgibt oder einen Piepser aktiviert. Damit erhalten wir eine indirekte aber klare Rückmeldung über den Erfolg der Transmission. Allgemeine Fehlerbehandlung: Fehler können bereits auftreten, noch bevor irgendwelche Daten übertragen wurden, sogar wenn gar keine Bootloader-Sitzung vorgesehen ist und eigentlich der normale Start der Applikations-Firmware erwartet wird. Denn jeder Kaltstart bringt es mit sich, dass Hardwarekomponenten und Portleitungen des Controllers für eine gewisse Zeit undefinierte Zustände einnehmen können. Der Brownout-Detektor des Mikrocontrollers stellt sicher, dass der Prozessor erst dann mit der Programmverarbeitung beginnt, wenn sich die Versorgungsspannung ausreichend stabilisiert hat; auf externe Hardwarekomponenten hat der BOD jedoch keinen Einfluss. So ist es durchaus möglich, dass gerade auf der für den seriellen Datenempfang vorgesehenen Leitung noch für längere Zeit ein unzulässiger oder undefinierter Pegel anliegt. Oder externe Hardware verursacht einen oder mehrere Pegelwechsel, weil sich die Versorgungsspannungen in externen Bausteinen noch nicht ausreichend stabilisiert haben. Wenn zu diesem Zeitpunkt das Bootloader-Programm bereits auf der Leitung lauscht, kann es also zu "Missverständnissen" kommen. Das ist mit dem TSB öfter passiert, besonders im Zusammenhang mit USB-Geräten, die nur durch Power-On initialisiert werden konnten. Hier gab es manchmal den unschönen Effekt, dass sich der Bootloader wegen eines ungültigen Anfangszustandes auf der Leitung "aufhängt". Auch hierfür gab es 'Workarounds', aber diese haben dann in anderen Hardwareumgebungen wieder Probleme bereitet. Man kann es eben nicht allen recht machen... Der OWL zeigt ein verbessertes und noch robusteres Startverhalten. Hier hat der Start der Applikation Vorrang. Sollte die Empfangsleitung direkt nach einem Kaltstart noch sehr "wackelig" sein, dann kommt keine Bootloader-Sitzung zustande, weil auch die saubere Datenübertragung infrage gestellt wäre, aber der OWL geht nach dem Abklingen der Störung in den normalen Timeout und startet dann die Applikations-Firmware. Wurde der erste Datenblock komplett entschlüsselt, und findet OWL dann wie erwartet die nächste Präambel vor, geht er davon aus, dass er sich in einer gültigen Transmission befindet und weitermachen kann. Sollte es in diesem fortgeschrittenen Stadium zu einem Timeout kommen, dann übergibt der Bootloader allerdings nicht an die Applikations-Firmware, sondern verbleibt im Blockade-Zustand. Die Fehlerbehandlung bzw. -meldung ist beim OWL also recht einheitlich geregelt: Ein bisschen Gekruschel auf der Leitung blockiert nicht den Bootloader und verhindert vor allem nicht den Start der regulären Applikation. Wenn aber eine gültige Transmission empfangen wird, dann kommt ENTWEDER alles vollständig, fehlerfrei und zügig durch und die Applikation wird unmittelbar nach Beendigung der Transmission gestartet; ODER es kommt zu irgendeinem Fehler oder Timeout während der Übertragung und der Bootloader geht in den Blockadezustand. Timeout-Timing: Der OWL-Timeout beginnt mit jedem Idle-Zustand auf der Leitung und funktioniert zu jedem Zeitpunkt, also auch innerhalb eines Zeichenrahmens, falls etwa die Übertragung abrupt unterbrochen wurde. Der Timeout soll in einem breiten Bereich von realistischen Taktraten (etwa 128 kHz bis 25 MHz) Verzögerungen von einigen hundertstel Sekunden bis hin zu mehreren Sekunden ermöglichen. Das ist nur möglich, indem die OWL-Software anhand der bekannten Taktfrequenz (aus der Target-Datei) einen entsprechenden Teilerfaktor berechnet, der dann direkt in der neu erzeugten Firmware eingebaut wird. Dieser Vorteiler ist auf eine Zeiteinheit von 1/100stel Sekunde normiert. Später kann der Timeout für jeden OWL-Bootloader über ein einziges Byte in der Maßeinheit 1/100 Sekunde (Centisekunden) angegeben werden. Ein Timeout-Byte mit dem Wert "1" entspricht also einer hundertstel Sekunde, der Wert "100" entspricht genau einer Sekunde, und der Wert "255" entspricht in etwa 2,5 Sekunden. Das spart beim nachträglichen Ändern des Timeouts (ATtinys) umständliche Rechnerei. PRNG (Schlüsselgenerator): Der PRNG wird bei Aufruf des Bootloaders mit dem geheimen Schlüssel geladen. Dieser innere Zustand des PRNG verändert sich bei fortlaufender Taktung und er wird außerdem über den Klartext jedes entschlüsselten Blockes modifiziert (siehe Diagramm zum Key-Feedback). Der PRNG ist die Software-Implementierung eines klassischen 128-Bit LFSR mit Rückführungen von den Bits 128, 127, 126 und 121, hocheffizient als Galois-XOR auf ein Register umgesetzt. Der Output geht durch ein sogenanntes Self-Shrinking-Filter. Diese Funktion macht aus dem einfachen LFSR mit eher mäßigen Autokorrelationseigenschaften einen kryptografisch starken Pseudozufallsgenerator. Allerdings braucht das SSG-LFSR im Durchschnitt 3 mal mehr Taktzyklen, als ein ordinäres LFSR. Dieser Nachteil erscheint in der Bootloader-Anwendung tolerierbar, da Sicherheit an erster Stelle steht und andere Faktoren den Datendurchsatz viel stärker drosseln. Die unregelmäßige Taktung könnte sogar noch Vorteile hinsichtlich möglicher Seitenkanalangriffe bedeuten. Nichtzuletzt ließ sich der Assemblercode für das SSG-LFSR deutlich optimieren. Man lernt ja dazu... Hier erfolgen die Bitshifts auf je zwei Arbeitsregistern (statt über alle 16), und nur mit jedem 8.Takt werden die übrigen 14 Register dann direkt als Bytes geshiftet (mov), was eine ganze Menge Prozessortakte einspart. Meines Wissens ist das vorliegende SSG-LFSR eine der takt- und codeeffizientesten CSPRNG-Umsetzungen für 8-Bit-MCUs. Port-Limits: Die Firmware kann nur auf I/O-Ports zugreifen, die über die Maschinenbefehle cbi, sbi, sbic, sbis direkt erreichbar sind. Einige exotische Devices kennen mittlerweile auch einen "PORTG" oder "PORTH" mit I/O-Adressen oberhalb von $3F. Diese Ports sind infolge der AVR-Prozessorarchitektur leider nur über SRAM-Befehle ansprechbar. Weil die entsprechenden Opcodes ein langsameres Timing haben und mehr Speicherplatz kosten, werden solche "memory mapped"-Zugriffe von der OWL-Firmware gegenwärtig nicht unterstützt. Code-Flexibilität: Der Assembler-Quelltext der OWL-Firmware enthält auch wieder einige Präprozessor-Anweisungen zur bedingten Assemblierung, ein Konzept, das sich schon beim TSB bewährt hat. Mit einem Quelltext kann eine ganze Reihe von Devices abgedeckt werden. Die Anzahl solcher .if/.endif-Konstrukte ist beim OWL sogar geringer, als beim TSB. Denn das zugrundeliegende Übertragungsformat, die "OWL-Transmission" mit maßgeschneiderten Präambeln bleibt für alle Devices einheitlich und berücksichtigt schon auf der Senderseite etwaige Besonderheiten im Timing, die sich aus der geringfügig unterschiedlichen Architektur der Devices ergeben. Außerdem verschwendet der OWL keine zusätzliche Speicherseite für Benutzerdaten (wie TSB, FastBoot u.a.). Weniger Extrawürste und aufgeräumterer Code. Die OWL-Firmware ist sogar wartungsfreundlicher, als das Vorgängerprojekt. Portierbarkeit: Der vorliegende Assemblercode ermöglicht ohne besondere Anpassungen die Erzeugung von OWL-Firmware für einen Kernbestand von ca. 120 ATtinys und ATmegas. Zahlreiche ATtinys und ATmegas sind in der Vergangenheit auch erfolgreich mit TSB gelaufen. Für einige Chips waren Anpassungen am Code erforderlich; für den OWL musste das Rad glücklicherweise nicht neu erfunden werden. Unterstützung für ATmega128x und ATmega256x: Der Zugriff auf Flash-Speicher über 64 KB ist bei den großen ATmegas durch eine Art Bankswitching (erweiterter Z-Pointer) geregelt. Entsprechende Modifikationen in der OWL-Firmware für diese ATmegas sind inzwischen ausgiebig getestet. Der lineare Adresszugriff wurde beibehalten. In einem Einweg-Szenario dürfte dies noch die sicherste und zuverlässigste Methode sein, um einen größeren Speicherbereich kontrolliert mit Daten zu befüllen. Aber es geht komfortabler, als bisher. Auf einem ATmega kann fehlerhafter Code oder Reste einer alten Firmware die Integrität und Verfügbarkeit des Bootloaders nicht gefährden, insbesondere, wenn zusätzlich der Schreibschutz für die Bootloader-Sektion aktiviert ist. Beim nächsten Reset ist ein ATmega-Bootloader auf jeden Fall wieder verfügbar. Daher wurde das vorausgehende Löschen des gesamten Applikations-Flash durch einen anderen Modus ersetzt: Seitenweises Überschreiben. Es werden nur diejenigen Flash-Pages gelöscht, die als Nächstes überschrieben werden sollen. Das bedeutet, wir müssen auch nicht immer die ganze Applikations-Firmware neu schreiben. Es besteht die Möglichkeit von abgestuften Updates. Wenn eine Applikation also tatsächlich die ganzen 128 oder 256 Kilobytes (abzüglich Bootloader-Sektion) in Anspruch nimmt, dann liegt es nahe, das Programm in Module aufzuteilen. Im höheren Adressbereich liegen dann diejenigen Programmteile, die nicht sehr häufig oder nie mehr geändert werden müssen (Libraries, Tabellen). Unten liegt die Haupt-Applikation mit Features, die im Rahmen von Firmware-Updates öfter mal aktualisiert werden sollen. Ein Update, das nur den unteren Flash abdeckt, lässt die weiter oben liegenden Speicherinhalte komplett unangetastet. Diese Methode bewahrt im Rahmen des Einweg-Zugriffs eine gewisse Flexibilität bei Firmware-Updates, ohne heilloses Chaos und Sicherheitsprobleme heraufzubeschwören. Es kann schon ein paar Minuten dauern, bis 128 oder gar 256 Kilobytes an nicht komprimierbaren verschlüsselten Daten mit 9600 Baud auf den Controller rübergeschaufelt sind. Hier zeigt sich, dass das robuste OWL-Verfahren bestens für solche langen Durchgänge geeignet ist. Genießen wir doch den Nervenkitzel und die Spannung, bis sich das Gerät zurückmeldet! Mit höheren Clockfrequenzen auf der Seite des Controllers erreicht OWL durchaus flotte Übertragungsgeschwindigkeiten, die so manchen ISP-Programmer übertrumpfen. Watchdog-Unterstützung: Das Zusammenspiel einer Bootloader-Firmware mit einer Applikations-Firmware, die den Watchdog im Interrupt- oder Reset-Modus nutzt, ist nicht ganz unproblematisch. Es gab aber reichhaltiges Feedback zu diesem Thema von Leuten, die den OWL in genau so einem Umfeld produktiv einsetzen wollten. Grund genug, sich näher damit zu befassen... In der Regel bleibt der Watchdog über einen Warmstart-Reset hinaus aktiv, aber auf einigen AVRs wird der Prescaler, also der Timer, der die WD-Ansprechzeit steuert, durch ein Reset-Ereignis gelöscht, sodass der WD anschließend leider im kürzestmöglichen Abstand erneut getriggert wird. Ist außerdem die Fuse WDTON gesetzt, lässt sich dieses Verhalten auch nicht mehr per Software abschalten. Der WD wird ausgesprochen zuverlässig, man könnte auch sagen: penetrant. Eine Applikations-Firmware, die den WD nutzt, muss in ihrer Reset-Routine zum frühestmöglichen Zeitpunkt den WD (re-)initialisieren. Geschieht das nicht oder nicht in korrekter Weise, so kann es zu dem berühmt-berüchtigten Problem von endlosen WD-Resets kommen, wodurch sich die Applikation dann selbst blockiert. Wenn ein Bootloader dazwischengeschaltet ist, muss dieser den WD abschalten oder zumindest kontrollieren können. TinySafeBoot hat einfach an die Applikation weitergeleitet, wenn das WD-Flag gesetzt war. Dadurch wurde TSB in Setups mit dauerhaft eingeschaltetem WD praktisch wertlos. Der Bootloader von Welt stellt sich dem Problem! Die verbesserte OWL-Firmware (ab 02/2019) versucht zunächst einmal, den WD auszuschalten. Das funktioniert immer, solange die WDTON-Fuse nicht per Hardware gesetzt war. Sollte diese Fuse aber gesetzt sein, lässt sich der WD also per Software grundsätzlich nicht abschalten, dann greift Plan B und OWL stellt den WD-Prescaler auf seinen Maximalwert (je nach Chip 2 oder 8 Sekunden). Im weiteren Verlauf der Sitzung wird OWL dann regelmäßig Watchdog-Timer-Resets ausführen, wie dies auch in der eigentlichen Applikation an verschiedenen Punkten geschehen muss, damit kein WD-Interrupt oder WD-Reset ausgelöst wird. Auf diese Weise kann der OWL sein gutes Werk ohne Störungen erledigen. Erst nachdem OWL die Kontrolle an die Applikations-Firmware zurückgegeben hat, wird diese im Rahmen ihrer Reset-Routine den Watchdog neu konfigurieren (müssen). Das wäre jedenfalls gute Programmierpraxis, davon muss ich an dieser Stelle einfach mal ausgehen. Generell gibt der OWL das Statusregister MCUSR unverändert weiter, sodass eine Applikation, genau wie in einer Installation ohne Bootloader, die verschiedenen Reset-Quellen unterscheiden und dementsprechend handeln kann. Selbstverständlich ist auch der Fehlerstatus (Blockade) gegen WD-Resets abgesichert und wird bis zum nächsten Hardware-Reset aufrechterhalten. Anwendungen mit mehreren OWL-AVRs an einer gemeinsamen Programmierleitung werden vermutlich durch das neue WD-Feature nicht eingeschränkt. Der Spaß kostete etwas Hirnschmalz, ein paar Diskussionen mit Anwendern, eine Reihe von zeitraubenden Versuchen am Steckbrett und ein Dutzend zusätzlicher Befehle im Code. Reset-Filter-Funktion: Die neue OWL-Firmware (08/2020) kann zwischen Reset-Quellen (Reset Sources) unterscheiden. Abhängig davon, ob der Controller durch 'Watchdog', 'BrownOut', 'External' oder 'PowerOn' resettet wurde, wird er entweder normal gestartet, wartet also auf ein Signal bis zum Timeout, oder er leitet praktisch sofort (nur wenige Taktzyklen Verzögerung) zur Applikations-Firmware weiter. Damit kann sich der Bootloader vornehm im Hintergrund halten, wenn beispielsweise eine Anwendung ein besonders präzises Reset-Timing erwartet. Technisch wird das neue Feature auf der Seite der Assemblerprogrammierung mit einer simplen Bitmaske realisiert, die das MCU Status Register nach Reset-Quellen auswertet. Konkret sind das ohnehin nur maximal 4 Flags im unteren Nibble (WDRF|BORF|EXTRF|PORF). Siehe AVR-Datenblätter! Die Software muss bei der Anpassung eines Firmware-Template also gegebenenfalls die richtige Stelle im Maschinencode ausfindig machen und den Default-Wert 0b00001111 gegen die vom User gewünschte Reset-Maske austauschen. Dafür steht im Target Make Mode ein neues Argument '--resets=' bzw. '-rs=' zur Verfügung. Beispiel: 'rs=1110' bedeutet, dass die Reset-Quellen 'Watchdog', 'BrownOut' und 'Extern' zugelassen sind, also den Bootloader starten würden. Dagegen wird der Bootloader bei einem 'PowerOn'-Reset sofort an die Anwendung abgeben. Im Falle eines Kaltstarts wird der OWL dann in diesem Beispiel nicht aktiviert, wohl aber beim Auslösen eines Resets per Reset-Leitung, Watchdog-Timer oder Unterspannung. Default ist immer '-rs=1111' bzw. die normale Maske, die alle Reset-Quellen zulässt. Wird RS also nicht konfiguriert, verhält sich der neue OWL hinsichtlich Resets exakt wie der alte. Reset-Filter-Umgehung: Darauf wäre ich ohne Benutzer-Feedback nie gekommen (04/2023). Es gibt einen Spezialfall, in dem sich die ansonsten bewährte Filtermethode etwas unvorteilhaft verhält: Sollten alle Bits im MCUSR beim Start von OWL den Wert "0" haben, wurde mit der bisherigen Firmware logischerweise immer an die Applikation weitergeleitet, denn das AND zur Prüfung des Bitmusters lieferte in diesem Fall immer 0. Der Fall kommt aber unter "natürlichen" Bedingungen NIE vor, da der Prozessorkern nach jedem hardwarebedingten Reset-Ereignis eines der Bits WDRF|BORF|EXTRF|PORF setzen wird. Nun hat mich ein professioneller Anwender auf folgende Idee gebracht: Eine Applikations-Firmware möchte ihrerseits an den Bootloader weiterleiten können, am besten über ein standardisiertes Verfahren und nur durch Sprung an die reguläre Bootloader-Startadresse, welche versionsunabhängig als Konstante betrachtet werden kann. Der naheliegende Workaround war, dass die Applikation einfach eines oder alle Bits setzt, die beim Bootloader als Reset-Quelle durchgelassen werden. (Das Löschen oder Setzen von MCUSR-Bits ist laut Datenblatt möglich, da alle Bits im unteren Nibble "R/W" gekennzeichnet sind. Für die oberen Bits gilt das leider nicht, sie sind "reserved" und liefern immer nur eine Null zurück.) Allerdings könnte das betreffende Bit ebensogut durch ein tatsächliches Reset-Ereignis gesetzt worden sein. Daraus ergibt sich eine gewisse Unsicherheit. Eine Applikation, die nach einer Bootloadersitzung gestartet wird, kann nicht so ohne Weiteres feststellen, ob die Bootloadersitzung vielleicht durch sie selbst ausgelöst wurde. Lösung: Der OWL umgeht bei MCUSR = 0 den RS-Check und startet direkt durch (d. h. Warten auf Signal bis Timeout). Diese Bedingung kann nur vorliegen, wenn vorher schon eine Applikations-Firmware gelaufen ist, das MCUSR gelöscht hat und zur Übergabe einen unbedingten Sprung nach BOOTSTART eingeleitet hat. Der OWL nimmt keine Veränderngen am MCUSR vor. Er wird nach dem Ende einer erfolgreichen Sitzung (nicht "Blockade") das "genullte" MCUSR wiederum an die alte oder neue Applikations-Firmware weitergeben. Die Applikation kann (und sollte) durch Auswertung des MCUSR wissen, was vor ihrem Start passiert ist. Das Beste, diese zusätzliche Möglichkeit für ein elegantes Hand-over beeinträchtigt nicht die sonstige RS-Prüfung. Für eine Handvoll Devices sind die zusätzlichen 4 Bytes derzeit leider zuviel. Hier stand ich vor der Entscheidung, entweder schmerzhafte und eventuell noch nicht ausreichend geprüfte weitere Optimierungen zu wagen, oder das neue Feature bei einigen Devices wegzulassen. Ich habe mich für Letzteres entscheiden. Möglicherweise lassen sich bei genauerer Analyse des Codes doch noch ein paar Byte einsparen, aber das will man nicht überstürzen... Die Liste der wenigen Controller, für die das OWL-Feature "MCUSR-Null" also vorerst nicht zur Verfügung steht, befindet sich im Changelog. Wohlgemerkt, diese Devices haben ansonsten den vollen Funktionsumfang, den die aktuelle OWL Firmware bietet. Fuses/Lockbits:Für den Betrieb eines Bootloaders muss der Controller gewisse Voraussetzungen erfüllen.ATtinys:
Kompatibilitäts-Voraussetzungen und Vorsichtsmaßnahmen:
Nach oben | Index |
Software
|
Hardware-OptionenViele Wege führen nach Flash und EEPROM...!
![]()
|
Algorithmus |
AVR-Implementierung |
Vorteile |
Nachteile |
AES (Rijndael) |
|
|
|
XTEA ("eXtended Tiny Encryption Algorithm") |
|
|
|
PRNG-XOR (Stromchiffre) |
|
|
|
RST ("Randomized Substitution-Transposition") |
|
|
|
(1) |
(2) |
(3) |
(4) |
![]() |
![]() |
![]() |
![]() |
Klartext unverschlüsselt: Bitmap-Grafik 200x200 Pixel, 8-Bit Graustufen, 40 kB. (Das entspricht 2500 Blöcken à 16 Bytes!) |
Verschlüsselt im ungeeigneten ECB-Modus. Jeder Block wurde mit demselben Schlüsselsatz verschlüsselt. Musterbildung im Chiffrat erkennbar. (Schlecht!) |
Verschlüsselt mit fortlaufendem Schlüsselgenerator. Keine Musterbildung, starkes Chiffrat. |
Entschlüsselt mit Klartext-Schlüssel-Rückkopplung und einem Übertragungsfehler: Nachfolgende Daten komplett unbrauchbar. Fehler oder Manipulation sicher erkannt! |
1. Voraussetzungen
OWL-Download für Dein Betriebssystem
|
2. PC-Installation der OWL-Software
Das Download-Paket einfach an den gewünschten Ort entpacken. Ins Verzeichnis 'owl' gehen und dort ein Konsolenfenster öffnen. Nach Eingabe von owl ohne irgendwelche Optionen müsste die allgemeine Hilfeseite durchscrollen. Wenn das funktioniert, dann geht vermutlich auch der Rest. (Unter Unixen "./" voranstellen, kennste!) |
3. Maßgeschneiderte OWL-Firmware erzeugenBeispiel: ATmega8 in einem typischen RS232-Setup (MAX232, FT232) mit Quarz 8 MHzDer Controller ist mit einem MAX232 (oder FT232) verbunden. Es werden die Portleitungen PD0/PD1 als RXD/TXD genutzt (Standard, wenn normalerweise der Hardware-UART benutzt wird). Die ordnungsgemäße Funktion der RS232-Anbindung sollte vorher schon getestet worden sein. Für eine LED an PB2 steht ein primitives Blinkprogramm zur Verfügung. Auch dieses am besten vorher schon einmal ganz ohne Bootloader hochladen und testen. Testplattform mit funktionierender RS232-Anbindung - check! LED-Blinker - check! Jetzt erzeugen wir für diese Hardware unseren ersten OWL-Bootloader. In diesem Beispiel verwendet der OWL also ebenfalls PD0 als RXD-Eingang. Die Kommandozeile zur Erzeugung des neuen OWL-Bootloaders lautet dann: owl --device=m8 --rxport=d0 --clock=8000 --baud=9600 --targetname=testowl_m8 Das war's auch schon! Die neue OWL-Firmware steht unter ./targets/testowl_m8.hex |
4. Installation OWL-Firmware (Bootloader)Wir starten ein ISP-Brennprogramm (z.B. "avrdudess", "extreme burner", "TwinAVR") und übertragen die neue Bootloader-Firmware "testowl_m8.hex" per ISP in den Flash-Speicher des Ziel-Controllers! Vorher bitte den GESAMTEN Flash löschen.Anschließend setzen wir die Fusebits, wofür wohl jedes Brennprogramm eine entsprechende Funktion anbietet. Schön auf die Zuordnung Low, High, Extended achten. Wichtigste Voraussetzung für einen Bootloader ist die Fähigkeit, Flash-Speicher per Software schreiben zu können (SPM). Auf ATtinys muss hierfür explizit Self-Programming (SELFPRGEN) aktiviert sein, auf ATmegas, wo Bootloader ja direkt unterstützt werden, muss dies nicht extra berücksichtigt werden. Vor allem sollte immer der Brown-Out-Detektor (BODEN) eingeschaltet sein. Damit ergibt sich ein sauberes Kaltstartverhalten, und die Gefahr von Flash-Korrumpierung wird minimiert. Für den Beispiel-ATmega8 mit externem 8-MHz-Quarz wären folgende Fuses eine gute Basis: BOOTSZ=10; BODEN=0; BODLEVEL=0; BOOTRST=0; CKSEL=1100; SUT=00 Byte-Werte: Ext: $FF Low: $8D High: $EC Tipp: Im Web gibts praktische Fusebit-Kalkulatoren, die wesentlich informativer und benutzerfreundlicher ausgeführt sind, als so manche integrierte "Hilfefunktion" von prominenten ISP-Programmiertools. Damit wäre unser neuer Bootloader auf dem Chip installiert! |
5. Test-Firmware übertragen
Für diesen Test sollte sich das Blinkprogramm ledblink_m8.hex im Hauptverzeichnis der OWL-Software befinden. Die Controller-Hardware sei in diesem Beispiel an COM2 angeschlossen. Standardmäßig sind beim Bootloader eine Sekunde Timeout eingestellt, die Transmission bekommt etwa eine Sekunde Intro-Präambel. Am besten ERST die Kommandozeile "abfeuern", DANN den Controller resetten: |
Info-KommandosKurzübersicht zu allen Befehlsoptionen:owl --help Lange Beschreibung zu allen Befehlsoptionen: owl --helpall Hilfe zu einer oder mehreren Optionen (hier: 'flasherase', 'timeout' und 'serialport'): owl --help --flasherase --timeout --serialport Liste aller direkt unterstützten AVR-Modelle: owl --supported Technische Daten zu bestimmtem AVR-Modell einsehen: owl --device=Modellname Stammdaten zu einem selbst erzeugten Bootloader abrufen (Beispiel): owl --targetname=testowl_m8 |
m1280 m1281 m1284 m1284P m128A m128 m128RFA1 m128RFR2 m162 m164A m164PA m164P m165A m165PA m165P m168A m168 m168PA m168P m169A m169PA m169P m16A m16 m16HVA m16HVB m16M1 m16U2 m16U4 m2560 m2561 m256RFR2 m324A m324PA m324P m3250A m3250 m3250PA m3250P m325A m325 m325PA m325P m328 m328PB m328P m3290A m3290 m3290PA m3290P m329A m329 m329PA m329P m32A m32C1 m32 m32HVB m32M1 m32U2 m32U4 m406 m48A m48 m48PA m48P m640 m644A m644 m644PA m644P m6450A m6450 m6450P m645A m645 m645P m6490A m6490 m6490P m649A m649 m649P m64A m64C1 m64 m64M1 m64RFR2 m8515 m8535 m88A m88 m88PA m88P m8A m8 m8HVA m8U2 tn13A tn13 tn1634 tn167 tn2313A tn2313 tn24A tn24 tn25 tn261A tn261 tn4313 tn441 tn44A tn44 tn45 tn461A tn461 tn48 tn80 tn840 tn841 tn84A tn84 tn85 tn861A tn861 tn87 tn88
|