C-Code, der sich selbst *im RAM* prüft

Lesezeit: 9 Minuten

Benutzer-Avatar
Nur Jeff

Ich versuche, ein RAM-residentes Image dazu zu bringen, sich selbst zu prüfen, was leichter gesagt als getan ist.

Der Code wird zunächst auf einer Cross-Development-Plattform kompiliert, wodurch eine .elf-Ausgabe generiert wird. Ein Dienstprogramm wird verwendet, um das Binärbild zu entfernen, und dieses Bild wird zusammen mit der Bildgröße zum Flashen auf der Zielplattform gebrannt. Wenn das Ziel gestartet wird, kopiert es die Binärdatei in die richtige Region des RAM und springt dorthin. Das Dienstprogramm berechnet auch eine Prüfsumme aller Wörter im Elf, die für RAM bestimmt sind, und auch das wird in den Flash gebrannt. Mein Image könnte also theoretisch sein eigenes RAM-residentes Image anhand der a-priori-Startadresse und der im Flash gespeicherten Größe prüfen und mit der im Flash gespeicherten Summe vergleichen.

So jedenfalls die Theorie. Das Problem ist, dass, sobald das Bild mit der Ausführung beginnt, eine Änderung in der erfolgt .data Abschnitt, wenn Variablen geändert werden. Wenn die Summe fertig ist, ist das summierte Bild nicht mehr das Bild, für das der Versorger die Summe berechnet hat.

Ich habe Änderungen aufgrund von Variablen, die von meiner Anwendung definiert wurden, eliminiert, indem ich die Prüfsummenroutine vor alle anderen Initialisierungen in der App verschoben habe (was sinnvoll ist, wenn eine Integritätsprüfung fehlschlägt, oder?), aber die Killer ist die C-Laufzeit selbst. Es scheint, dass es einige Elemente gibt, die sich darauf beziehen malloc und Pointer-Casting und andere Dinge, die zuvor geändert wurden main() sogar eingetragen ist.

Ist die ganze Idee der Selbstprüfsumme von C-Code lahm? Wenn es eine Möglichkeit gäbe, app und CRT .data in verschiedene Abschnitte zu zwingen, könnte ich den CRT-Thrash vermeiden, aber man könnte argumentieren, dass, wenn das Ziel darin besteht, das Image auf Integrität zu prüfen, bevor es (das meiste davon) ausgeführt wird, die initialisierten CRT-Daten sollten ein Teil davon sein. Gibt es überhaupt eine Möglichkeit, Code-Prüfsummen selbst im RAM zu erstellen?

FWIW, ich scheine mit einer Anforderung dafür festgefahren zu sein. Persönlich hätte ich gedacht, dass der richtige Weg darin besteht, die Binärdatei zu prüfen in dem Blitz, vor der Übergabe an den Stößel, und vertrauen Sie dem Lader und dem Stößel. Paranoia muss irgendwo enden, oder?

Verschiedene Details: Werkzeugkette ist GNU, Bild enthält .text, .rodata und .data als ein zusammenhängend geladener Block. Es gibt kein Betriebssystem, dies ist Bare-Metal-Embedded. Hauptlader im Wesentlichen memcpyist meine Binärdatei in den RAM, an einer vorgegebenen Adresse. Es finden keine Umzüge statt. VM wird nicht verwendet. Die Prüfsumme muss nur einmal bei init getestet werden.


Aktualisiert
Habe das dadurch gefunden..

__attribute__((constructor)) void sumItUp(void) {
    // sum it up
    // leave result where it can be found
}

..dass ich vor der summe fast alles erledigen kann ausser der initialisierung der malloc/sbrk vars von der CRT-Init und einige vars, die “impure.o” und “locale.o” gehören. Jetzt die malloc/sbrk value ist etwas, das ich aus dem Projekt-Linker-Skript kenne. Wenn impure.o und locale.o entschärft werden könnten, könnten sie im Geschäft sein.

aktualisieren
Da ich den Einstiegspunkt steuern kann (durch das, was im Flash für den primären Lader angegeben ist), scheint es jetzt der beste Angriffswinkel zu sein, ein Stück benutzerdefinierten Assembler-Code zu verwenden, um Stack- und Sdata-Zeiger einzurichten, die Prüfsummenroutine aufzurufen und verzweigen Sie dann in den “normalen” _start-Code.

  • Haben Sie nicht die Möglichkeit, den Code in ein anderes Segment als .data einzufügen? Auf diese Weise können Sie Änderungen bei der Berechnung Ihrer Prüfsumme vollständig vermeiden.

    – Oliver Charlesworth

    31. Dezember 2012 um 15:01 Uhr


  • Ich könnte einfach die .text- und .rodata-Dateien summieren, aber dies würde Dinge wie initialisierte Tabellen usw. aus der Summe auslassen.

    – Just Jeff

    31. Dezember 2012 um 15:05 Uhr

  • Ja das stimmt. In diesem Fall haben Sie ein Problem. Vielleicht gibt es eine Möglichkeit, den CRT-Start vollständig zu untergraben und vorher Ihre Prüfsummenroutine auszuführen, aber das geht weit über mein Fachwissen hinaus! Um welche Plattform handelt es sich? Gibt es eine Möglichkeit, den Loader selbst zu ändern?

    – Oliver Charlesworth

    31. Dezember 2012 um 15:08 Uhr


  • Drat. Hatte auf so etwas wie -Wl,-weird_corner_case gehofft. Nun gut, „Requirements Pushback“ beginnt zu gefallen

    – Just Jeff

    31. Dezember 2012 um 15:16 Uhr

  • Warum übrigens das Datensegment in die Prüfsumme einbeziehen? Das Wesentliche in Bezug auf die Sicherheit ist sicherzustellen ausführbarer Code wurde nicht verändert.

    Benutzer529758

    31. Dezember 2012 um 15:19 Uhr

Wenn die Prüfsumme FRÜH genug erstellt wird, können Sie NUR Stack-Variablen verwenden und nicht in Datenabschnittsvariablen schreiben – das heißt, ALLES machen, was Sie zum Ausführen der Prüfsumme benötigen [and all preceding steps to get to that point] Verwenden Sie NUR lokale Variablen zum Speichern von Dingen [you can read global data of course].

Ich bin ziemlich davon überzeugt, dass der richtige Weg darin besteht, dem Flash & Loader zu vertrauen, dass er lädt, was sich im Flash befindet. Wenn Sie den Code prüfen möchten, gehen Sie und tun Sie das [assuming it’s not being modified by the loader of course – for example runtime loading of shared libraries or relocation of the executable itself, such as random virtual address spaces and such]. Aber auf die aus dem Flash geladenen Daten kann man sich nicht mehr verlassen, sobald die Ausführung richtig gestartet wurde.

Wenn es eine Anforderung von jemand anderem gibt, dass Sie dies tun sollten, dann erklären Sie dieser Person bitte, dass dies nicht durchführbar ist und dass „die Anforderung in ihrer jetzigen Form“ „gebrochen“ ist.

  • Ich denke, “früh genug” ist der Schlüssel, und zu diesem Zweck sieht es so aus, als wäre der beste Weg, eine Prise Assembler zu verwenden, um die Summierung durchzuführen, bevor der _start-Punkt aufgerufen wird. Es scheint schwer vorstellbar zu sein, dass ein Fehlermodus dies erfordern würde, aber ich war nicht “im Team”, als die Anforderungen/das Design festgelegt wurden.

    – Just Jeff

    1. Januar 2013 um 3:13 Uhr

  • Ich würde noch mit dem Urheber der Anfrage diskutieren, es kann sein, dass es ein Missverständnis darüber ist, was (leicht) getan werden kann. Irgendein Assembler sollte es auf jeden Fall machen, aber ich denke nur, dass es ein bisschen mehr Arbeit als nötig sein könnte.

    – Mats Petersson

    1. Januar 2013 um 10:52 Uhr

  • Dies “früh genug” zu akzeptieren ist die eigentliche Lösung, die ich verwende; fand den Weg, einen Assembler-Stub zu bekommen, um die Drecksarbeit zu erledigen und dann in CRT zu springen. Keine reine C-Lösung, aber es hat am Ende funktioniert.

    – Just Jeff

    3. Januar 2013 um 15:47 Uhr

Ich würde vorschlagen, dies wie einen ausführbaren Packer anzugehen, wie z upx.

In den anderen Antworten und in Ihrer Frage gibt es mehrere Dinge, die mich mangels eines besseren Begriffs nervös machen.

  • Ich würde dem Loader oder irgendetwas im Flash nicht vertrauen, das mir nicht aufgezwungen wurde.
  • Im Internet kursiert ein Quellcode, der verwendet wurde, um eines der neuesten Telefone von HTC zu sichern. Schauen Sie sich auf forum.xda-developers.com um und sehen Sie, ob Sie es finden und als Beispiel verwenden können.
  • Diese Forderung würde ich zurückdrängen. Mobiltelefonhersteller verbringen viel Zeit damit, ihre Bilder zu sperren, und schließlich werden sie alle geschlagen. Das scheint ein Teufelskreis zu sein.

  • nicht wirklich versuchen, irgendetwas zu sperren oder zu verhindern, dass das System gerootet wird oder so etwas, dies ist nur eine direkte Integritätsprüfung, weil jemand befürchtet hat, dass etwas beschädigt werden könnte während des Kopierens in den RAM

    – Just Jeff

    31. Dezember 2012 um 21:30 Uhr

  • Ich würde vorschlagen, eine robustere Integritätsprüfung als eine einfache Prüfsumme zu verwenden. Zumindest könnten Sie CRC32 oder kryptografische Hash-Funktionen wie MD5 oder SHA problemlos verwenden

    – Marko

    1. Januar 2013 um 20:30 Uhr

  • @JustJeff: Es ist durchaus möglich, dass es beschädigt wird, aber ziemlich unwahrscheinlich. Wir hatten in den 90er Jahren ein solches Problem mit Modems, aber es trat nur etwa einmal in 4 Millionen Transaktionen auf. Es hat fast 2 Jahre gedauert, bis die App 4 Millionen Transaktionen gesehen hat und wir das Problem gefunden haben. Trotzdem mussten wir es reparieren. Ich glaube immer noch, dass der Exe-Packer-Ansatz seinen Wert hat, und so würde ich ihn angehen, wenn ich mich an diese Anforderung halten würde.

    – JimR

    3. Januar 2013 um 9:55 Uhr

  • Ich hätte crc32 vorgezogen, bin aber bei der Prüfsumme hängen geblieben. So oder so wäre das Problem das gleiche gewesen; der Code modifiziert Daten, bevor der gewählte Algorithmus ausgeführt werden konnte. Wie auch immer, so interessant wie dieser Exe-Packer klingt, denke ich, dass er in meinem Fall wahrscheinlich übertrieben ist, aber für andere ein Segen sein könnte.

    – Just Jeff

    3. Januar 2013 um 15:44 Uhr

Können Sie das Linker-Skript verwenden, um impure.o und locale.o vor oder nach allem anderen zu platzieren, sodass Sie alles außer diesen und dem malloc/sbrk-Zeug prüfen können? Ich vermute malloc und sbrk werden im Bootloader aufgerufen, der Ihre Anwendung lädt, sodass der von diesen verursachte Thrash nicht beseitigt werden kann?

Es ist keine Antwort, Ihnen einfach zu sagen, dass Sie gegen diese Anforderung ankämpfen sollen, aber ich stimme zu, dass dies übertrieben zu sein scheint. Ich bin mir sicher, dass Sie nicht ins Detail gehen können, aber ich gehe davon aus, dass die Spezifikationsautoren eher über böswillige Benutzer / Hacker besorgt sind als über normale Speicherbeschädigungen aufgrund von kosmischer Strahlung usw. In diesem Fall, wenn es sich um eine böswillige handelt Benutzer/Hacker können ändern, was in den RAM geladen wird, sie können einfach Ihre Prüfsummenroutine ändern (die selbst aus dem RAM läuft, richtig?), um immer einen zufriedenen Status zurückzugeben, egal wie gut die Prüfsummenroutine, die sie nicht mehr ausführen, entworfen ist .

Selbst wenn sie sich Sorgen über eine normale Speicherbeschädigung machen, würde diese Prüfsummenroutine nur dann abfangen, wenn der Fehler während der ursprünglichen Kopie in den Speicher aufgetreten ist, was eigentlich der unwahrscheinlichste Zeitpunkt ist, an dem ein solcher Fehler auftritt, einfach weil das System nicht ausgeführt wurde lange genug, um eine hohe Wahrscheinlichkeit für ein Korruptionsereignis zu haben.

  • Klingt absurd, nicht wahr? Wenn die Flash-Prüfung in Ordnung ist und der RAM-Test bestanden wird, könnte das Einzige, was während des Kopierens selbst schief gehen könnte, sein, dass das CPU-Register, das zum Aufnehmen und Zurückschreiben der Daten verwendet wird, ein schlechtes Bit hat, was höchstwahrscheinlich zu größeren Ergebnissen führen würde mehr als nur eine verstümmelte Kopie. Der Verstand verwirrt.

    – Just Jeff

    1. Januar 2013 um 15:01 Uhr

Im Allgemeinen ist das, was Sie tun möchten, unmöglich, da der Programmlader auf vielen (den meisten?) Plattformen einige Programmadressenkonstanten “verschieben” kann.

Können Sie den Loader aktualisieren, um den Prüfsummentest für das Flash-residente Binärimage durchzuführen, bevor es in den RAM kopiert wird?

  • Oder erstellen Sie vielleicht ein sekundäres Ladeprogramm, das Ihr Hauptprogramm überprüft, bevor es geladen wird.

    – kkrabo

    31. Dezember 2012 um 21:22 Uhr

  • Oder erstellen Sie vielleicht ein sekundäres Ladeprogramm, das Ihr Hauptprogramm überprüft, bevor es geladen wird.

    – kkrabo

    31. Dezember 2012 um 21:22 Uhr

1097380cookie-checkC-Code, der sich selbst *im RAM* prüft

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy