blog · git · desktop · images · contact & privacy · gopher


NX-Bit ausprobiert

Noch nie wirklich ausprobiert. Jetzt nachgeholt!

/* basic io */
#include <stdio.h>

/* mmap */
#include <sys/mman.h>

/* bcopy */
#include <strings.h>

/*
 * Diese Funktion wird später in einen Speicherbereich kopiert, der
 * mittels mmap() angefordert wurde. Dabei kann man angeben, ob der
 * Speicher ausführbar sein soll oder nicht.
 *
 * Ist die Funktion ausführbar, dann hat das NX-Bit nicht funktioniert.
 */
void test(unsigned char *where)
{
    where[0] = 'J';
    where[1] = 'A';
    where[2] = '.';
}

/*
 * Diese Funktion muss direkt nach test() kommen. Ich verlasse mich
 * darauf, dass sie später im Speicher auch direkt hinter test()
 * erscheint. Damit lässt sich die tatsächliche Code-Größe von test()
 * messen.
 */
void dummy()
{
}

int main(int argc, char *argv[])
{
    void *pTest, *pDummy;
    int szTest;
    void *testArea = NULL;
    void (*fptr)(unsigned char *);
    unsigned char writeHere[] = "000";

    /* Pointer auf die zwei Funktionen und Größe messen. */
    pTest = test;
    pDummy = dummy;
    szTest = pDummy - pTest;

    printf("Adresse von test:  %p\n", pTest);
    printf("Adresse von dummy: %p\n", pDummy);
    printf("Größe von test:  %d\n", szTest);

    /* Speicher anfordern, der *KEIN* PROT_EXEC hat. */
    testArea = mmap(0, szTest, PROT_READ | PROT_WRITE,
            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (testArea == MAP_FAILED)
    {
        perror("mmap fehlgeschlagen");
        return 1;
    }

    printf("Pointer auf Testbereich: %p\n", testArea);

    /* Kopiere den Code der Funktion in den neuen Speicher. */
    bcopy(pTest, testArea, szTest);

    /* Belege den Funktionspointer mit der neuen Adresse und springe
     * dann dorthin. */
    printf("Inhalt: '%s'\nSpringe in Testbereich.\n", writeHere);
    fptr = (void (*)(unsigned char *))testArea;
    fptr(writeHere);

    /* Hat es geklappt? Wie sieht der Speicher jetzt aus? */
    printf("Zurück.\nInhalt: '%s'\n", writeHere);

    return 0;
}

Schaut man sich das Speicherlayout zur Laufzeit an, dann hat der angeforderte Bereich keine Ausführungsrechte:

Pointer auf Testbereich: 0xb78dc000
...
$ grep '^b78dc' /proc/7370/maps 
b78dc000-b78df000 rw-p 00000000 00:00 0

Trotzdem funktioniert ohne NX-Bit (also zum Beispiel unter i686 ohne PAE-Kernel) der Sprung, er führt die Funktion aus und kehrt normal zurück. Mit x86_64 hingegen funktioniert das Beispiel nur, wenn man zusätzlich PROT_EXEC angibt.