Trampolinspringen mit gcc

2006年01月04日 (水曜日) 04時20分

Als jemand der auf Windows (bzw. DOS) mit Programmieren angefangen hat empfand ich unter POSIX-Systemen immer das dauernde gepipe (und damit verbunden: geparse) am schlimmsten. Und wenn mir das jetzt ernsthaft irgendwer als gut verkaufen will darf er sich den Sourcecode des RIPE-whois angucken. (den Teil mit der GPG-Anbindung)

Quasi als Windows-Nachwirkung benutze ich sehr gern libdl und eine handvoll .so-Dateien. Nur hat man da wiederum das Problem, dass fehlerhafte Module die ganze Applikation zum Absturz bringen können. Unter Windows kann man COM-Objekte in separaten Adressräumen laufen lassen, und wenn das SEGVed kracht halt das COM-Objekt weg, aber nicht die ganze Anwendung. Ich dachte sehr lange, unter GNU/Linux müsste man die selbe Methodik nachbauen - also Threads benutzen - bis mir auf einer Zugfahrt vor paar Wochen die Idee zu dem Code hier gekommen ist:

loader.c:
static int callmod(int op)
{
	__label__ out;
	struct sigaction new, oldsegv, oldbus;
	int rv = 0;

	void sigh(int sig, siginfo_t *si, void *info)
	{
		fprintf(stderr, "sig%d accessing %p in module\n",
			sig, si->si_addr);
		rv = 0xDEADBEEF;
		goto out;
	}

	new.sa_sigaction = sigh;
	new.sa_flags = SA_SIGINFO | SA_RESETHAND | SA_NODEFER;
	sigemptyset(&new.sa_mask);

	sigaction(SIGSEGV, &new, &oldsegv);
	rv = fn(op);
out:
	sigaction(SIGSEGV, &oldsegv, NULL);
	return rv;
}

(Wer das auf Anhieb verstanden hat braucht den Rest nicht mehr zu lesen.)

Dass das ganze überhaupt geht, ermöglicht etwas gcc-Magic - verschachtelte Funktionen (void sigh(...)) gibt es leider in ANSI-/ISO-C nicht. Der Trick ist, dass gcc aus solchen Funktionen in die enthaltende Funktion herausspringen kann, von wo aus wir dann normal weiterarbeiten können.

Zur Umsetzung des ganzen verwendet gcc Trampolines, denen grsecurity-Benutzer vielleicht schon begegnet sind. Als Trampoline wird hier Code bezeichnet, der auf den Stack geschoben wird sobald man die Adresse der inneren Funktion haben will.

Die Problematik ist hier, dass die innere Funktion nicht direkt per Adresse des Codes aufgerufen werden kann. Zum Aufruf der Funktion benötigt man nicht wie normalerweise eine Information - die Adresse der Funktion - sondern zwei Informationen: ihre Adresse und die Adresse des Stack-Frames der äusseren Funktion. Es müssen also zwei Informationen in eine gequetscht werden. Das geschieht, indem man zur Laufzeit die Adresse des Stackframes zusammen mit dem Aufruf der eigentlichen Funktion auf den Stack schiebt, und dann die Adresse dieses Fetzchens übergibt.

Die Adresse des Stack-Frames der äusseren Funktion wird im Normalfall genutzt, um von der inneren Funktion auf lokale Variablen der äusseren Funktion zuzugreifen, also im Codebeispiel die Zuweisung von 0xDEADBEEF an rv. In diesem Fall wird sie jedoch für etwas anderes benutzt: als "Save-Point" nach dem Absturz. Falls nun also irgendwo weiter unten im Code eine SEGV auftritt, kann man den Stack der aufgerufenen Funktionen abwerfen und zum definierten Punkt (__label__ out) mit intaktem Stack zurückkehren.

Trampolin-Erzeugung und resultierender Code:
mov  eax, offset sigh
lea  rcx, [rsp+496]
lea  rdx, [rsp+524]
mov  word [rdx],     0bb41h
mov dword [rdx+2], eax
mov  word [rdx+6],   0ba49h
mov qword [rdx+8], rcx
mov  word [rdx+16],  0ff49h
mov  byte [rdx+18],  0e3h

und als Ergebnis hat man auf dem Stack:

41 bb ????????          mov r11, 0x????????          # sigh
49 ba ????????????????  mov r10, 0x????????????????  # frame
49 ff e3                jmp [r11]
Stack als Grafik

Ein Allheilmittel ist das hier allerdings nicht. Sollte ein Stack Overflow passieren oder globale Variablen verändert werden, ist der "Save-Point" wertlos...

Kommentareintrag:

Hackish C

2006年01月15日 (日曜日) 21時13分
Copyright © 2005, 2006, 2007 by David L. (equinox)
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.0 Germany License
Creative Commons License

Impressum

      mode   entry
      uri    trampolining
      offset 0