Ich mache einen College-Kurs, in dem eine der Übungen darin besteht, Pufferüberlauf-Exploits für Code durchzuführen, den sie uns geben. Dies reicht von einfachen Exploits wie dem Ändern der Rücksprungadresse für eine Funktion auf einem Stack, um zu einer anderen Funktion zurückzukehren, bis hin zu Code, der den Status eines Programmregisters / -speichers ändert, aber dann zu der von Ihnen aufgerufenen Funktion zurückkehrt Die von Ihnen aufgerufene Funktion kennt den Exploit nicht.
Ich habe einige Nachforschungen angestellt, und diese Arten von Exploits werden auch heute noch fast überall verwendet, zum Beispiel bei der Ausführung von Homebrew auf der Wii und dem Jailbreak ohne Tethering für iOS 4.3.1
Meine Frage ist, warum dieses Problem so schwer zu beheben ist? Es ist offensichtlich, dass dies ein wichtiger Exploit ist, der verwendet wird, um Hunderte von Dingen zu hacken, aber es scheint ziemlich einfach zu sein, Eingaben über die zulässige Länge hinaus zu kürzen und alle von Ihnen aufgenommenen Eingaben zu bereinigen.
BEARBEITEN: Eine andere Perspektive, über die ich Antworten haben möchte - warum beheben die Entwickler von C diese Probleme nicht, indem sie die Bibliotheken neu implementieren?
quelle
Es ist nicht wirklich ungenau zu sagen , dass C tatsächlich „fehleranfällig“ durch ist Design . Abgesehen von einigen schwerwiegenden Fehlern wie
gets
, kann die C-Sprache nicht anders sein, ohne das Hauptmerkmal zu verlieren, das die Leute an erster Stelle zu C zieht.C wurde als Systemsprache entwickelt, um als eine Art "tragbare Baugruppe" zu fungieren. Ein Hauptmerkmal der C-Sprache ist, dass C-Code im Gegensatz zu höheren Sprachen häufig sehr genau dem tatsächlichen Maschinencode zugeordnet ist. Mit anderen Worten, es
++i
handelt sich in der Regel nur um eineinc
Anweisung, und Sie können häufig anhand des C-Codes eine allgemeine Vorstellung davon bekommen, was der Prozessor zur Laufzeit tun wird.Das Hinzufügen impliziter Grenzwertprüfungen erhöht jedoch den Overhead, den der Programmierer nicht verlangt hat und möglicherweise nicht wünscht. Dieser Overhead geht weit über den zusätzlichen Speicherplatz hinaus, der zum Speichern der Länge jedes Arrays erforderlich ist, oder über die zusätzlichen Anweisungen zum Überprüfen der Array-Grenzen bei jedem Array-Zugriff. Was ist mit Zeigerarithmetik? Oder was ist, wenn Sie eine Funktion haben, die einen Zeiger aufnimmt? Die Laufzeitumgebung kann nicht erkennen, ob dieser Zeiger innerhalb der Grenzen eines legitim zugewiesenen Speicherblocks liegt. Um dies zu verfolgen, benötigen Sie eine seriöse Laufzeitarchitektur, die jeden Zeiger mit einer Tabelle der aktuell zugewiesenen Speicherblöcke vergleicht. Zu diesem Zeitpunkt befinden wir uns bereits im Bereich der verwalteten Java / C # -Stil-Laufzeit.
quelle
Ich denke , das eigentliche Problem ist , zu beheben nicht , dass diese Art von Fehlern hart ist, aber dass sie so einfach zu machen: Wenn Sie verwenden
strcpy
,sprintf
und Freunde in die (scheinbar) einfachste Art und Weise, arbeiten kann, dann haben Sie wahrscheinlich öffnete die Tür für einen Pufferüberlauf. Und niemand wird es bemerken, bis jemand es ausnutzt (es sei denn, Sie haben sehr gute Codeüberprüfungen). Fügen Sie jetzt die Tatsache hinzu, dass es viele mittelmäßige Programmierer gibt und dass sie die meiste Zeit unter Zeitdruck stehen - und Sie haben ein Rezept für Code, das so voller Pufferüberläufe ist, dass es schwierig sein wird, sie alle zu beheben, nur weil es solche gibt so viele von ihnen und sie verstecken sich so gut.quelle
sizeof(ptr)
ist in der Regel 4 oder 8. Dies ist eine weitere C-Einschränkung: Es gibt keine Möglichkeit, die Länge eines Arrays zu bestimmen, wenn nur der Zeiger darauf angegeben wird.#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))
das eine Division durch Null während der Kompilierung auslöst. Eine andere clevere, die ich zum ersten Mal in Chromium gesehen habe, ist#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))
die, bei der eine Handvoll falscher Positive gegen einige falsche Negative getauscht werden - für char [] ist sie leider nutzlos. Sie können verschiedene Compiler-Erweiterungen verwenden, um die Zuverlässigkeit zu erhöhen , z . B. blogs.msdn.com/b/ce_base/archive/2007/05/08/… .Es ist schwierig, Pufferüberläufe zu beheben, da C praktisch keine nützlichen Tools zur Behebung des Problems bietet. Es ist ein grundlegender Sprachfehler, dass die nativen Puffer keinen Schutz bieten und es praktisch, wenn nicht sogar vollständig, unmöglich ist, sie durch ein überlegenes Produkt zu ersetzen, wie es C ++ mit
std::vector
und getan hatstd::array
, und es ist selbst im Debug-Modus schwierig, Pufferüberläufe zu finden.quelle
std::vector
effizient implementiert werden. Undvector::operator[]
macht die gleiche Wahl für Geschwindigkeit über Sicherheit. Die Sicherheit bestehtvector
darin, dass das Karren um die Größe erleichtert wird, wie es auch in modernen C-Bibliotheken der Fall ist.realloc
(C99 ermöglicht es Ihnen auch, Stack-Arrays mithilfe einer laufzeitbestimmten, aber konstanten Größe über eine beliebige automatische Variable zu dimensionieren, die fast immer bevorzugt wirdchar buf[1024]
). Zweitens hat das Problem nichts mit dem Erweitern von Puffern zu tun. Es hat damit zu tun, ob Puffer die Größe mit sich führen und diese Größe prüfen, wenn Sie darauf zugreifen.vector::operator[]
macht die Überprüfung Grenzen im Debug - mode- etwas nativen Arrays nicht do- und zweitens können, gibt es keine Möglichkeit , in C auszulagern den nativen Array - Typen mit einem, kann die Überprüfung der Grenzen tun, denn es gibt keine Vorlagen ist und keinen Operator Überlastung. In C ++, wenn Sie von zu verschiebeT[]
zustd::array
, können Sie praktisch tauschen nur ein typedef. In C gibt es keine Möglichkeit, dies zu erreichen, und es gibt keine Möglichkeit, eine Klasse mit äquivalenter Funktionalität zu schreiben, geschweige denn eine Schnittstelle.std::vector<T>
undstd::array<T, N>
tut in C ++. Es gäbe keine Möglichkeit, eine Bibliothek zu entwerfen und anzugeben, auch keine Standardbibliothek, die dies könnte.std::vector
kann er auch niemals statisch dimensioniert werden. Was generisches betrifft, können Sie es so generisch machen, wie es gut sein muss. C benötigt es - eine kleine Anzahl grundlegender Operationen für void * (Hinzufügen, Entfernen, Ändern der Größe) und alles andere, was speziell geschrieben wurde. Wenn Sie sich darüber beschweren, dass C keine Generika im C ++ - Stil enthält, liegt dies weit außerhalb des Rahmens der sicheren Pufferbehandlung.Das Problem liegt nicht in der C- Sprache .
IMO, das einzige große Hindernis, das es zu überwinden gilt, ist, dass C einfach schlecht unterrichtet ist . Jahrzehntelange Missbräuche und falsche Informationen wurden in Referenzhandbüchern und Vorlesungsskripten verankert und haben den Verstand jeder neuen Generation von Programmierern von Anfang an vergiftet. Die Schüler erhalten eine kurze Beschreibung der "einfachen" E / A-Funktionen wie
gets
1 oderscanf
und werden dann ihren eigenen Geräten überlassen. Ihnen wird nicht gesagt, wo oder wie diese Tools ausfallen können oder wie diese Ausfälle verhindert werden können. Ihnen wird nicht gesagt, wie manfgets
und benutztstrtol/strtod
weil diese als "fortgeschrittene" Werkzeuge gelten. Dann werden sie in die Berufswelt entlassen, um ihr Chaos anrichten zu können. Nicht viele der erfahreneren Programmierer wissen es besser, weil sie die gleiche gehirngeschädigte Ausbildung erhalten haben. Es ist verrückt. Ich sehe hier und auf Stack Overflow und auf anderen Websites so viele Fragen, bei denen klar ist, dass die Person, die die Frage stellt, von jemandem unterrichtet wird, der einfach nicht weiß, wovon er spricht , und natürlich kann man das nicht einfach sagen "Ihr Professor ist falsch", weil er ein Professor ist und Sie nur ein Typ im Internet sind.Und dann haben Sie die Menge, die jede Antwort, die mit "nun ja, gemäß dem Sprachstandard ..." beginnt, verachtet, weil sie in der realen Welt arbeiten und gemäß ihnen der Standard nicht für die reale Welt gilt . Ich kann mit jemandem umgehen, der nur eine schlechte Ausbildung hat, aber jeder, der darauf besteht , unwissend zu sein, ist nur eine Panne für die Branche.
Es würde keine Pufferüberlaufprobleme geben, wenn die Sprache korrekt unterrichtet würde , wobei der Schwerpunkt auf dem Schreiben von sicherem Code liegt. Es ist nicht "schwer", es ist nicht "fortgeschritten", es ist nur vorsichtig.
Ja, das war eine Schande.
1 Zum Glück wurde es endgültig aus der Sprachspezifikation gestrichen, obwohl es in 40 Jahren alten Codes für immer lauern wird.
quelle
sprintf
, aber das bedeutet nicht, dass die Sprache nicht fehlerhaft ist. C war fehlerhaft und ist fehlerhaft - wie jede Sprache - und es ist wichtig, dass wir diese Fehler zugeben, damit wir sie weiterhin beheben können.Das Problem liegt in der Kurzsichtigkeit des Managements und nicht in der Inkompetenz der Programmierer. Denken Sie daran, dass eine Anwendung mit 90.000 Zeilen nur einen unsicheren Vorgang benötigt, um vollständig unsicher zu sein. Es ist fast unmöglich, dass eine Anwendung, die über eine grundlegend unsichere String-Behandlung geschrieben wurde, zu 100% perfekt ist - was bedeutet, dass sie unsicher ist.
Das Problem ist, dass die Kosten für die Unsicherheit entweder nicht dem richtigen Adressaten in Rechnung gestellt werden (das Unternehmen, das die App verkauft, muss den Kaufpreis so gut wie nie erstatten) oder zum Zeitpunkt der Entscheidungsfindung nicht klar ersichtlich sind ("Wir müssen versenden") im März egal was! "). Ich bin mir ziemlich sicher, dass das Schreiben in C oder verwandten Sprachen viel teurer wäre, wenn Sie die langfristigen Kosten und Kosten für Ihre Benutzer berücksichtigt hätten und nicht für Ihr Unternehmen. Wahrscheinlich so teuer, dass es in vielen Fällen eindeutig die falsche Wahl ist Felder, in denen heutzutage konventionelle Weisheit sagt, dass es eine Notwendigkeit ist. Dies wird sich jedoch erst ändern, wenn eine strengere Softwarehaftpflicht eingeführt wird - was niemand in der Branche wünscht.
quelle
Eine der großen Stärken von C ist, dass Sie damit das Gedächtnis so manipulieren können, wie Sie es für richtig halten.
Eine der großen Schwächen bei der Verwendung von C ist, dass Sie damit das Gedächtnis so manipulieren können, wie Sie es für richtig halten.
Es gibt sichere Versionen von unsicheren Funktionen. Programmierer und Compiler erzwingen ihre Verwendung jedoch nicht strikt.
quelle
Wahrscheinlich, weil C ++ dies bereits getan hat und abwärtskompatibel mit C-Code ist. Wenn Sie also einen sicheren Zeichenfolgentyp in Ihren C-Code einfügen möchten, verwenden Sie einfach std :: string und schreiben Ihren C-Code mit einem C ++ - Compiler.
Das zugrunde liegende Speichersubsystem kann dazu beitragen, Pufferüberläufe zu verhindern, indem Schutzblöcke eingeführt und deren Gültigkeit überprüft werden. Bei allen Zuordnungen werden also 4 Byte 'fefefefe' hinzugefügt. Wenn in diese Blöcke geschrieben wird, kann das System einen Wobbler auslösen. Es ist nicht garantiert, dass ein Speicherschreiben verhindert wird, aber es zeigt, dass ein Fehler aufgetreten ist und behoben werden muss.
Ich denke, das Problem ist, dass die alten strcpy usw.-Routinen noch vorhanden sind. Wenn sie zugunsten von strncpy usw. entfernt würden, würde das helfen.
quelle
Es ist leicht zu verstehen, warum das Überlaufproblem nicht behoben ist. C war in einigen Bereichen fehlerhaft. Zu der Zeit wurden diese Mängel als erträglich oder sogar als Merkmal angesehen. Jetzt, Jahrzehnte später, sind diese Mängel nicht mehr zu beheben.
Einige Teile der Programmiergemeinschaft möchten nicht, dass diese Löcher verstopft werden. Schauen Sie sich alle Flammenkriege an, die über Strings, Arrays, Zeiger, Garbage Collection ... beginnen.
quelle
memcpy()
Verfügbarkeit und der Tatsache, dass dies nur die Standardmethode zum effizienten Kopieren eines Arraysegments ist.