Stack-Canary-Eliminierungsexperiment

Feb 27 2023
Inspiriert von Walter Harold Bishop werde ich in diesem Beitrag versuchen, einen besseren Weg zum Schutz der Integrität von Absenderadressen im Vergleich zu Stack Canaries zu finden. Bitte beachten Sie, dass ich nicht glaube, dass das, was ich mir vorgestellt habe, die perfekte Lösung ist.

Inspiriert von Walter Harold Bishop werde ich in diesem Beitrag versuchen, einen besseren Weg zum Schutz der Integrität von Absenderadressen im Vergleich zu Stack Canaries zu finden. Bitte beachten Sie, dass ich nicht glaube, dass das, was ich mir vorgestellt habe, die perfekte Lösung ist. Es gibt viele Dinge, die ich noch nicht gründlich durchdacht habe. Wie auch immer, Sie können dies als „Versuch Nr. 1“ betrachten.

Ich mag keine Stack-Canaries, weil sie die eigentliche Ursache des Problems nicht lösen. Sogar die Wikipedia-Seite bringt es in die richtige Perspektive. Lassen Sie mich zitieren:

Diese Technik kann die Schwierigkeit, einen Stapelpufferüberlauf auszunutzen, erheblich erhöhen

„Erhöhen Sie die Schwierigkeit der Ausbeutung“. Das Problem wird dadurch nicht behoben. Es erhöht die Schwierigkeit der Ausbeutung.

Zum Zeitpunkt des Verfassens dieses Beitrags schreiben wir das Jahr 2023. Sie können noch Folgendes tun:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char *payload =
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA";
    char buffer[32];
    memcpy(buffer, payload, (int)strlen(payload));
    printf("%s\n", buffer);
}

(lldb) target create "./test"
Current executable set to '/Users/zsoltimre/DEVEL/EXPERIMENT/Virtual-CPU/test' (arm64).
(lldb) r
Process 15890 launched: '/Users/zsoltimre/DEVEL/EXPERIMENT/Virtual-CPU/test' (arm64)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Process 15890 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x19c4c4bb0)
    frame #0: 0x000000019c4c4bb0 libsystem_c.dylib`__chk_fail_overflow + 24
libsystem_c.dylib`:
->  0x19c4c4bb0 <+24>: brk    #0x1
libsystem_c.dylib`:
    0x19c4c4bb4 <+0>:  pacibsp 
    0x19c4c4bb8 <+4>:  stp    x29, x30, [sp, #-0x10]!
    0x19c4c4bbc <+8>:  mov    x29, sp
Target 0: (test) stopped.

Ich beschloss, etwas anderes zu untersuchen und damit zu experimentieren. Was wäre, wenn wir Absenderadressen vollständig aus dem Stapel entfernen würden? Im Großen und Ganzen würde dies bedeuten, dass Angreifer den Ausführungsfluss nicht ändern oder das Programm nicht zum Absturz bringen könnten, indem sie die Rücksprungadresse ändern. Ich gebe zu, dass eine solche Lösung ihren Preis hat, aber sie löst das Problem.

Also, meine Idee und der stark vereinfachte Proof of Concept in Kürze:

  • Rücksprungadressen werden in einem dedizierten Speicherbereich (RSTACK) gespeichert, der sich wie der Stack verhält. RSTACK ist für Benutzercode schreibgeschützt. Nur die CPU kann Werte aus RSTACK pushen und entfernen. Im PoC wird dies durch die Verwendung eines dedizierten Arrays erzwungen .
  • Es gibt ein dediziertes Linkregister (Return Pointer/RP), das auf den Anfang von RSTACK zeigt. Dieses Register ist für Benutzercode schreibgeschützt. Nur die CPU kann seinen Wert ändern.
  • Die CALL*- und RET* -Anweisungen der CPU verwalten RSTACK.
  • Keine der anderen Anweisungen kann in RSTACK schreiben.
Der Anfangszustand der CPU

Schauen wir uns an, wie der CPU-Status und der Speicher direkt nach dem CALL-Befehl aussehen.

Wenn wir dann die Anweisungen von Adresse 7 ausführen lassen und den Stapel beschädigen, wenn wir den letzten RET-Befehl ausführen, landen wir genau dort, wo wir wollten, bei Adresse 4. Egal, was wir mit dem Stapel machen, wir können immer zurückkehren und Ausführung fortsetzen.

In einer realen Anwendung ist es sehr wahrscheinlich, dass die Beschädigung der Werte auf dem Stapel andere Probleme verursachen würde (z. B. Division durch Null oder Nullzeiger-Dereferenzierung), die zum Absturz des Programms führen würden. Daher behebt diese Lösung nicht alle Denial-of-Service-Probleme. Aber,

  • Es verhindert perfekt, dass ein Angreifer den Ausführungsfluss kontrollieren kann.
  • Auch Brute-Force-Angriffe gegen den Kanarienvogel entfallen vollständig, da es keine Kanarienvögel mehr gibt.
  • Ein Grund weniger für den Absturz eines Programms aufgrund von Korruption.

Wie auch immer, es hat mir Spaß gemacht, damit herumzuspielen, und ich hoffe, Sie hatten Spaß beim Lesen dieses Beitrags. Bis zum nächsten Mal!