2015-10-23 12 views
49

In einigen Exploits für die Root-Shell bekommen, habe ich oft einen solchen Zeiger sehen:Erläuterung eines Zeigers in Exploit-Code

int i; 
unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191); 

Kann jemand diese Zeiger ein wenig erklären? Ich denke 8191 ist die Größe des Kernel-Stacks. zeigt auf den unteren Rand der Kernel-Stack? Hier ist, wie Zeiger p verwendet wird:

int i; 
unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191); 
for (i = 0; i < 1024-13; i++) { 
    if (p[0] == uid && p[1] == uid && 
     p[2] == uid && p[3] == uid && 
     p[4] == gid && p[5] == gid && 
     p[6] == gid && p[7] == gid) { 
      p[0] = p[1] = p[2] = p[3] = 0; 
      p[4] = p[5] = p[6] = p[7] = 0; 
      p = (unsigned *) ((char *)(p + 8) + sizeof(void *)); 
      p[0] = p[1] = p[2] = ~0; 
      break; 
     } 
    p++; 
} 
+3

Der Wert '8191' im Binärformat ist' 1111111111111' und der 'Long' Typ ist 32 Bit. Ich denke, um dir eine feste Antwort zu geben, müssten wir sehen, wie der "* p" -Zeiger verwendet wird. Der Operator '&' ist wahrscheinlich eine Art Maske. –

+0

@TimBiegeleisen Danke für Ihre Antwort. Ich habe es bearbeitet. – HuangJie

Antwort

54

Der Code nimmt die Adresse der lokalen Variablen i einen Zeiger in den aktuellen Stack-Frame zu erhalten. Dann richtet es die Adresse auf die 8K-Seite aus (das ist, was Sie tun mit x & ~8191: 8191 ist 2^13-1, was bedeutet, dass ~8191 alle Einsen sind mit Ausnahme der niedrigen 13 Bits, so AND es mit einer Nummer wird die niedrigen 13 Bits löschen, dh richten Sie die Zahl auf das nächstniedrigere Vielfache von 2^13 aus, also auf die 8K-Grenze.

Er nimmt dann diese Adresse und interpretiert sie als Zeiger auf einen Zeiger und lädt die angegebene Adresse daraus. Weitere Informationen finden Sie unter Understanding the getting of task_struct pointer from process kernel stack.

Danach versucht es, eine bestimmte Struktur irgendwo hinter dieser Adresse zu finden: Es sieht durch die folgenden 1024-13unsigned s, versucht, einen Platz im Speicher zu finden, wo die aktuellen Prozessinformationen (wahrscheinlich) gespeichert ist: Wenn es findet Ein Stück Speicher, der mehrere Kopien der aktuellen UID und GID enthält, nimmt an, dass er es gefunden hat. In diesem Fall ändert es es so, dass der aktuelle Prozess UID und GID 0 erhält, wodurch der Prozess unter root ausgeführt wird (und alle one-ones in den folgenden capability Flags gespeichert werden).

Cf. struct cred.

8

Ich werde noch eine weitere Antwort posten, weil hier wirklich etwas hinzuzufügen ist.

unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191); 

führt dazu, dass p der Zeiger auf den Anfang des 8192 Bytes großen Speicherblocks ist. Der Code ist jedoch falsch. Wenn p über INT_MAX liegt (was es sein könnte oder würde es in vorzeichenlose, nicht vorzeichenlose Umwandlung umgewandelt werden), werden die hohen Bits durch die Maske abgeschert. Korrekter Code ist wie folgt:

unsigned *p = *(unsigned**)(((ptrdiff_t)&i) & ~(ptrdiff_t)8191); 

oder mit uintptr_t:

unsigned *p = *(unsigned**)(((uintptr_t)&i) & ~(uintptr_t)8191U); 

Es ist notwendig zu gieße Zeiger für den Code Ganzzahl- und zurück zu arbeiten; Um jedoch einen Zeiger von int-Größe zu garantieren, muss ptrdiff_t verwendet werden (wir erinnern daran, dass sich signed und unsigned für bitweise Operationen genau gleich verhalten). Warum sie sie nicht mit hex Konstanten schreiben, wen interessierts. Die Typen, die solche Dinge tun, kennen ihre Kräfte auswendig. Es kann schneller sein, 8191 dann 0x1FFF zu lesen.

+1

Für gewissenhafte ISO-Korrektheit, 'uintptr_t' statt' ptrdiff_t' an beiden Stellen. Alle Linux-ABIs garantieren jedoch 'sizeof (unsigned long) == sizeof (T *)' für alle T. Die * minimal * -Änderung, um den Code im Kontext eines Linux-Kernel-Exploits korrekt zu machen, ist einfach '& ~ 8191UL 'statt' & ~ 8191'. – zwol

+0

Wenn '~ 8191' vom Typ' int' im '&' -Ausdruck mit einem 'unsigned long' auf der linken Seite verwendet wird, wird die Zeichenerweiterung angewendet.Daher werden die hohen Bits nicht von der Maske abgeschert. Dies ist der gleiche Grund, warum 'uint64_t x = -1; 'alle 64 Bits auf 1 setzt. – mortehu

+1

@mortehu: Ich habe das fragment woanders gebrannt. Wenn int weniger Bits als unsigned hat, machen die Konvertierungen das Falsche. – Joshua

Verwandte Themen