5.0 KiB
Typische Sicherheitslücken
Im Folgenden finden Sie eine kleine Auflistung von Sicherheitslücken, die sich gut in Crackmes verwenden können. Die Liste ist natürlich nicht vollständig - lassen Sie Ihrer Kreativität freien Lauf. Gerne können Sie auch noch weitere Lücken vorschlagen.
Buffer-Overflow
Lokale Variablen überschreiben
Buffer auf dem Stack liegen dort zusammen mit allen anderen lokalen Variablen und können diese bei fehlender oder falscher Prüfung der Grenzen überschreiben (Buffer-Overflow).
int i = 0;
char x[3];
printf("%d\n", i); // -> 0
strcpy(x, "Hell");
printf("%d\n", i); // -> 108
Rücksprungadresse überschreiben
Buffer-Overflows können auch dazu benutzt werden, die Rücksprungadresse zur Aufrufenden Funktion zu überschreiben. Stack-Guards sollen dies verhindern, können aber manchmal übergangen oder explizit durch -fno-stack-protector
beim Compilieren ausgeschaltet werden.
Stackbasierte Schwachstellen
Base-Pointer
Auf dem Stack liegt nicht nur die Rücksprungadresse, sondern auch der gespeicherte Base-Pointer (EBP
) der vorhergehenden Funktion. Wenn man diesen durch einen Buffer-Overflow modifizieren kann, besteht die Möglichkeit lokale Variablen der aufrufenden Funktion zu verändern.
Reste auf dem Stack
Der Stack wird bei einem Funktionsaufruf nicht gesäubert. Wenn die Funktion ihre Variablen nicht initialisiert, kann man möglicherweise alte Daten auf dem Stack entdecken.
int a(void) {
short a = 0xbeef;
short b = 0xdead;
}
int b(void) {
int z;
printf("0x%x\n", z);
}
int main() {
a();
b(); // -> 0xbeefdead
}
String-Funktionen
Größe von String-Puffern
Wegen des Nullbytes muss der Buffer für einen String immer mindestens ein Zeichen länger sein, als der String selbst. Wenn man dies nicht berücksichtigt, kommt es Buffer-Overflows.
char *text = "Hallo";
char *copy = alloc(strlen(text)); /* zu klein */
strcpy(copy, text); /* Buffer overflow */
Länge von Strings
Die normalen String-Funktionen (strcpy
. strlen
...) lesen bis zum ersten 0-Byte ('\0'
) im char*
-Array. Dies kann man eventuell ausnutzen, um mehr Daten zu verändern als vom Entwickler gewollt, weil man z.B. einen Puffer bis zum letzten Byte füllt.
int k = 0x47554755;
char src[] = { 'H', 'a', 'l', 'l', 'o' };
char dest[30];
strcpy(dest, src);
printf("%s", dest); // -> HalloUGUGJ
Puffer überschreiben
Funktionen wie gets
, strcpy
und sprintf
überprüfen die Größe des Zielpuffers nicht und lassen sich deswegen hervorragend für Buffer-Overflows nutzen.
int i = 0;
char b[5];
gets(b);
printf("%d\n", i);
Deswegen sollten die Funktionen nicht mehr genutzt werden. Findet man sie in den Imports eines Programms lohnt sich die Suche nach der Stelle, an der sie verwendet werden.
Integer-Overflow
Alle Integer-Datentypen ohne unsigned
(char
, short
, int
, long
, long long
) sind vorzeichenbehaftet und laufen von Plus nach Minus über.
int i = 2147483647;
i++;
printf("%d\n", i); // -> -2147483648
Alle Integer-Datentypen mit unsigned
laufen zur 0
über und zum Max-Value unter.
unsigned int i = 0;
i--;
printf("%u\n", i); // -> 4294967295
i++;
printf("%u\n", i); // -> 0
Formatstring-Schwachstelle
Formatstringschwachstellen (printf
) erlauben es, Variablen auf dem Stack zu lesen und zu schreiben.
int main() {
long k = 0xcafebabe;
long *p = &k;
printf("0x%9$x"); // -> 0xcafebabe
}
Funktionspointer
Funktionspointer auf dem Stack können, wenn sie durch eine der anderen Schwachstellen modifiziert werden können, dazu genutzt werden beliebige Funktionen aufzurufen.
void f() {
puts("f called");
}
int main() {
void (*fp)(void);
fp = f;
fp(); // -> f called
}
"malloc can never fail"
malloc()
gibt 0
im Fehlerfall zurück, dies sollte eigentlich vom Programm geprüft werden. Wird dies nicht geprüft, arbeitet das Programm mit einem Pointer weiter, der die Adresse 0x00
hat.
void *p = malloc(1717272222772);
printf("%p\n", p); // (nil)
Use-After-Free
Mit malloc
allozierter Speicher wird mit free
wieder freigegeben. Wenn man den Speicher aber nach dem free
noch weiter benutzt, können beliebige Daten von anderen Teilen des Programms dort landen, weil malloc
den Speicher natürlich "recycled". Hierdurch kann man Daten exfiltrieren oder Funktionen sogar eigene Daten unterschieben.
void printer() {
static char *text = 0;
if (!text) {
text = (char*) malloc(sizeof(char) * 9);
strcpy(text, "mutti123");
}
printf("%s\n", text); /* use after free */
free(text);
}
int main(int argc, char** argv) {
printer(); /* -> mutti123 */
char *pwnd = (char*) malloc(sizeof(char) * 9);
strcpy(pwnd, "pwned!");
printer(); /* -> pwned! */
}
GOT.PLT
Wenn das Programm -z,norelro
compiliert ist, kann man die Funktionen im global offset-table (GOT.PLT) ersetzen, so man durch eine andere Schwachstelle Zugriff darauf bekommt. Hierdurch kann man dann Standard-Funktionen der Library durch eigene ersetzen.