Frage

Ich weiß also, dass Linux vier Standardsegmente für einen X86 -Prozessor (Kernelcode, Kerneldaten, Benutzercode, Benutzerdaten) verwendet, aber alle haben dieselbe Basis und Grenze (0x00000000 und 0xfffff), was bedeutet Satz linearer Adressen.

Warum sollten Sie auch Benutzer-/Kernel -Segmente haben? Ich verstehe, warum es separate Segmente für Code und Daten geben sollte (nur weil der X86 -Prozessor mit den CS- und DS -Registern umgeht), aber warum nicht ein einzelnes Codesegment und ein einzelnes Datensegment haben? Der Speicherschutz erfolgt durch Paging, und die Segmente des Benutzer- und Kernel -Segmente sind ohnehin auf dieselben linearen Adressen geordnet.

War es hilfreich?

Lösung

Die X86 -Architektur assoziiert mit jedem Segmentdeskriptor einen Typ und eine Privilegie. Der Typ eines Deskriptors ermöglicht es Segmenten, nur Lesen, Lesen/Schreiben, ausführbare Dateien usw. zu erstellen, aber der Hauptgrund für verschiedene Segmente mit derselben Basis und Grenze besteht darin, eine andere Deskriptor -Privilegie (DPL) zu ermöglichen.

Das DPL besteht aus zwei Bits, sodass die Werte 0 bis 3 codiert werden können. Wenn die Privilegienniveau 0 beträgt, dann soll es sein Ring 0, was am privilegiertesten ist. Die Segmentdeskriptoren für den Linux -Kernel sind Ring 0, während die Segmentdeskriptoren für den Benutzerraum Ring 3 sind (am wenigsten privilegiert). Dies gilt für die meisten segmentierten Betriebssysteme. Der Kern des Betriebssystems ist Ring 0 und der Rest Ring 3.

Der Linux -Kernel setzt, wie Sie bereits erwähnt haben, vier Segmente ein:

  • __Kernel_cs (Kernel -Code -Segment, Base = 0, Limit = 4GB, Typ = 10, dpl = 0)
  • __Kernel_ds (Kernel -Datensegment, Base = 0, Limit = 4GB, Typ = 2, dpl = 0)
  • __User_cs (Benutzercode -Segment, Base = 0, Limit = 4GB, Typ = 10, dpl = 3)
  • __User_ds (Benutzerdatensegment, Base = 0, Limit = 4GB, Typ = 2, dpl = 3)

Die Basis und die Grenze aller vier sind gleich, aber die Kernelsegmente sind dpl 0, die Benutzersegmente sind dpl 3, die Codesegmente sind ausführbar und lesbar (nicht beschreibbar), und die Datensegmente sind lesbar und beschreibbar (nicht ausführbar). .

Siehe auch:

Andere Tipps

Die X86 -Speichermanagementarchitektur verwendet sowohl Segmentierung als auch Paging. Sehr ungefähr ist ein Segment eine Aufteilung des Adressraums eines Prozesses, der eine eigene Schutzrichtlinie hat. In der X86 -Architektur ist es also möglich, den Speicherbereich zu teilen, den ein Prozess in mehrere zusammenhängende Segmente sieht, und jeweils unterschiedliche Schutzmodi zuzuweisen. Paging ist eine Technik zum Abbau kleiner (normalerweise 4 KB) Regionen des Adressraums eines Prozesses zu Stücken des realen, physischen Gedächtnisses. Paging steuert somit, wie Regionen in einem Segment auf physischen RAM zugeordnet werden.

Alle Prozesse haben zwei Segmente:

  1. Ein Segment (adressiert 0x00000000 bis 0xBffffffff) für Benutzerebene, prozessspezifische Daten wie den Code des Programms, statische Daten, Heap und Stack. Jeder Prozess hat ein eigenes, unabhängiges Benutzersegment.

  2. Ein Segment (Adressen 0xc0000000 bis 0xffffffffffsfff), das Kernelspezifische Daten wie die Kernel-Anweisungen, Daten und einige Stapel enthält, auf denen Kernelcode ausgeführt werden kann, und interessanterweise ist ein Region in diesem Segment direkt dem physischen Speicher zugeordnet, so dass Der Kernel kann direkt auf physische Speicherorte zugreifen, ohne sich um die Adressübersetzung zu kümmern. Das gleiche Kernelsegment wird in jeden Prozess zugeordnet, aber Prozesse können nur bei der Ausführung im geschützten Kernelmodus darauf zugreifen.

Im Benutzer-Modus kann der Prozess also nur auf Adressen von weniger als 0xC0000000 zugreifen. Jeder Zugriff auf eine höhere Adresse als dies führt zu einem Fehler. Wenn jedoch ein Benutzermodusprozess im Kernel ausgeführt wird (zum Beispiel nach dem Aufrufen eines Systems), wird das Schutzbit in der CPU in den Supervisor-Modus geändert (und einige Segmentierungsregister werden geändert), was bedeutet, dass der Prozess ist, dass der Prozess ist Dadurch können Sie über 0xC0000000 auf Adressen zugreifen.

Beziehen sich aus: HIER

In x86 - Linux -Segmentregister werden für die Überprüfung des Pufferüberlaufs verwendet [siehe unten, die einige Zeichen -Arrays in Stack definiert haben]:

static void
printint(int xx, int base, int sgn)
{
    char digits[] = "0123456789ABCDEF";
    char buf[16];
    int i, neg;
    uint x;

    neg = 0;
    if(sgn && xx < 0){
        neg = 1;
        x = -xx;
    } else {
        x = xx;
    }

    i = 0;
    do{
        buf[i++] = digits[x % base];
    }while((x /= base) != 0);
    if(neg)
        buf[i++] = '-';

    while(--i >= 0)
        my_putc(buf[i]);
}

Wenn wir nun die Entmischung des Code GCC-generierten Code sehen.

Dump des Assembler -Codes für Funktionsdrucke:

 0x00000000004005a6 <+0>:   push   %rbp
   0x00000000004005a7 <+1>: mov    %rsp,%rbp
   0x00000000004005aa <+4>: sub    $0x50,%rsp
   0x00000000004005ae <+8>: mov    %edi,-0x44(%rbp)


  0x00000000004005b1 <+11>: mov    %esi,-0x48(%rbp)
   0x00000000004005b4 <+14>:    mov    %edx,-0x4c(%rbp)
   0x00000000004005b7 <+17>:    mov    %fs:0x28,%rax  ------> obtaining an 8 byte guard from based on a fixed offset from fs segment register [from the descriptor base in the corresponding gdt entry]
   0x00000000004005c0 <+26>:    mov    %rax,-0x8(%rbp) -----> pushing it as the first local variable on to stack
   0x00000000004005c4 <+30>:    xor    %eax,%eax
   0x00000000004005c6 <+32>:    movl   $0x33323130,-0x20(%rbp)
   0x00000000004005cd <+39>:    movl   $0x37363534,-0x1c(%rbp)
   0x00000000004005d4 <+46>:    movl   $0x42413938,-0x18(%rbp)
   0x00000000004005db <+53>:    movl   $0x46454443,-0x14(%rbp)

...
...
  // function end

   0x0000000000400686 <+224>:   jns    0x40066a <printint+196>
   0x0000000000400688 <+226>:   mov    -0x8(%rbp),%rax -------> verifying if the stack was smashed
   0x000000000040068c <+230>:   xor    %fs:0x28,%rax  --> checking the value on stack is matching the original one based on fs
   0x0000000000400695 <+239>:   je     0x40069c <printint+246>
   0x0000000000400697 <+241>:   callq  0x400460 <__stack_chk_fail@plt>
   0x000000000040069c <+246>:   leaveq 
   0x000000000040069d <+247>:   retq 

Wenn wir nun die stapelbasierten Zeichen -Arrays aus dieser Funktion entfernen, erzeugt GCC diese Wachprüfung nicht.

Ich habe dasselbe von GCC selbst für Kernel -Module gesehen. Grundsätzlich habe ich einen Absturz gesehen, als ich einen Kernelcode botte, und er fuhr mit virtueller Adresse 0x28. Später stellte ich fest, dass ich dachte, ich hätte den Stapelzeiger korrekt initialisiert und das Programm richtig geladen. Ich habe nicht die richtigen Einträge in GDT, was den FS basierend in eine gültige virtuelle Adresse übersetzen würde.

Im Falle eines Kernel -Codes ignorierte er jedoch einfach den Fehler, anstatt zu etwas wie __stack_chk_fail@plt> zu springen.

Die relevante Compiler -Option, die diese Wache in GCC hinzufügt, ist der Fundprotektor. Ich denke, dies ist standardmäßig aktiviert, um eine Benutzer -App zu erstellen.

Für Kernel können wir dieses GCC -Flag über die Option CONFOR CC_StackProtector aktivieren.

config CC_STACKPROTECTOR
 699        bool "Enable -fstack-protector buffer overflow detection (EXPERIMENTAL)"
 700        depends on SUPERH32
 701        help
 702          This option turns on the -fstack-protector GCC feature. This
 703          feature puts, at the beginning of functions, a canary value on
 704          the stack just before the return address, and validates
 705          the value just before actually returning.  Stack based buffer
 706          overflows (that need to overwrite this return address) now also
 707          overwrite the canary, which gets detected and the attack is then
 708          neutralized via a kernel panic.
 709
 710          This feature requires gcc version 4.2 or above.

Die relevante Kerneldatei, in der dieses GS/FS Linux/Arch/x86/include/asm/stackProtector ist.h ist

Der Kernel -Speicher sollte nicht aus Programmen lesbar sein, die im Benutzerbereich ausgeführt werden.

Programmdaten sind häufig nicht ausführbar (DEP, eine Prozessorfunktion, mit der es hilft, einen überflüssigen Puffer und andere böswillige Angriffe auszuführen).

Es geht um Zugangskontrolle - verschiedene Segmente haben unterschiedliche Rechte. Aus diesem Grund gibt Ihnen der Zugriff auf das falsche Segment einen "Segmentierungsfehler".

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top