Suse 9.0 + Speicherreservierung für char(stack)

Nebuchadnezar

Erfahrenes Mitglied
HI ich hoffe ich bin hier im richtigen Forum da die Frage etwas Themenübergreifend ist.
Ich beschäftige mich gerade mit Pufferüberläufen (keine Sorge habe nichts böses damit vor) und in Linux ist mir bezüglich der Speicherreservierung für variablen eine Kuriosität aufgefallen.
Normalerweise wird ein Pufferüberlauf bei char Variablen bzw. die Funktionen dafür ausgenutzt, wenn keine Grenzen überprüft werden. Chars werden immer um ein vielfaches von 32bit reserviert (leichter zum adressieren für die CPU).
also bsp:
Code:
char buf[18];
intern wird dieser Puffer auf 20 erweitert (5x4 = 20)
-> wenn ich in diesen Puffer 28 mal a hinschreibe, wird die Rücksprungadresse überschrieben (eip = 0x61616161)
(4 byte ebp + 4 byte eip)
So funktioniert das bei der X86 Architektur - steht in jedem Tutorial und ich konnte das in Windows einwandfrei beobachten.
Jedoch wird bei mir mit Suse 9.0 viel mehr Speicher reserviert
(im Gegensatz zu den Linux Tutorials bezüglich Pufferüberläufen)
selbes beispiel wie oben:
18 -> 0x12
0x12 wird auf 0x20 erweitert + 8 byte
-> 18 = 0x12 -> 0x20 -> 0x28 = 40 byte
oder:
33 = 0x21 -> 0x30 -> 0x38 = 56 byte

Daraus schloss ich: wenn ich in ein char buf[18] 48 mal
"a" reinschreibe wird der eip mit 0x61616161 überschrieben. Diese Schlussfolgerung war richtig, jedoch möchte ich gern wissen wieso das bei Suse 9.0/ 9.2 so ist. (Diese Beobachtung konnte ich im Internet mit keinem Tutorial bestätigen.)
Hat jemand ähnliche Erfahrungen gemacht?
Bye.
 
Hallo,

gleich vorweg ich kenn mich mit der Thematik Pufferüberläufe nicht wirklich aus.
Aber das mit der Erweiterung zu einer 4 Byte Grenze erscheint mir irgendwie
komisch:

Das Programm:

C:
#include <stdio.h>

int main(){
    int a = 0;
    char test[18];
    int b = 0;
    printf("stack address of b\t=%p\n", &b);
    printf("stack address of test\t=%p\n", test);
    printf("stack address of a\t=%p\n", &a);
    return 0;
}

Ergibt folgende Ausgabe bei mir auf einem Debian System:
Code:
stack address of b      =0xbf8545ac
stack address of test   =0xbf8545b2
stack address of a      =0xbf8545c4

Sprich die Differenz zwischen der Addresse von a und der Addresse von test sind
genau 18 bytes und keine 20...

btw. meinst du mit dem Wort "ebp" den Framepointer?

//nocheinedit: Ein X86 Prozessor sollte doch sowohl Byte, Wort und Langwort- Adressierbarkeit
beherrschen? Also auch aus dem Gesichtspunkt macht das mit der Auffüllung eines char- Arrays auf
eine 4 Byte Grenze wenig Sinn, aber ich lass mich gerne eines besseren belehren :)

Gruß,
RedWing
 
Zuletzt bearbeitet:
Seltsam ^^
Hab dein Programm probiert und ich komm auf genau die 20 Byte nur um meine Aussage noch weiter zu untermauern.
Welchen Compiler/Os hast du benutzt?
Das sollte Aufklärung geben
Puffer Überlauf
besonders dieser Absatz:
bar()
{
char buf[10];

strcpy(buf, "Dies ist ein ordentlich langer String.");
}

Man kann feststellen, dass ein Array aus 10 char in Wirklichkeit 12 Byte beanspruchen, weil auf i386
genau 32Bit = 4Byte haben. Wenn jetzt strcpy(3) den zu langen String in das kleine Array schreibt,
passen die ersten 10 Zeichen wunderbar rein. Die nächsten 2 passen auch noch rein, weil ja der Platz
des Array mit 12 veranschlagt wurde.

Jedoch die nächsten vier überschreiben den alten Base Pointer (BP), und die darauffolgenden 4 überschreiben
auch noch den Instruction Pointer (IP).

ebp ist der Basepointer. Wenn mit Framepointer der Beginn des Stacks gemeint ist ja.
Code:
______________________
|_______%esp _________|
|_________int__________|
|________.....__________|
|________char_________|
|_______%ebp_________|
|_______%eip__________|
das wär da Stackframe bei deinem programm...so in etwa ohne die 2te integer

wenn die variabeln in einer unterfunktion reserviert werden würden(^^^)
zb
Aufruf: func();
würde bei %eip die Adresse nach dem Aufruf von func() gespeichert werden.
wenn func() wieder verlassen wird erfolgt wenn das ganze disassembliert wird ein
ret Befehl. Dabei wird in den Eip die gespeicherte adresse zurückgeschrieben und die ausführung geht wieder im Hauptprogramm weiter.
Da würd dann der ganze Spass anfangen.
Du schreibst in nen 20byte Puffer ein paar instruktionen rein (Shellcode einschläußen) den rest füllst auf 24 byte mit "a" auf und in die nächsten 4 schreibst den beginn der Adresse deiner char Variable. Wenn das programm func() verlassen will springt der IP in die char Variable rein und führt den eingeschläußten Code aus
-> du bringst dem Programm Dinge bei die es gar nicht wusste :-D.
 
Zuletzt bearbeitet:
RedWing hat gesagt.:
Ergibt folgende Ausgabe bei mir auf einem Debian System:
Beim Compiler geh ich einfach mal vom gemeinhin ueblichen GCC aus.

Evtl. interessant koennten aber die Versionen von GlibC und GCC sein, und evtl. auch die Kernel-Version, aber ich denk der Kernel wird da eher weniger Einfluss haben.
 
Hmmm dann haben wir schon mal 3 Versionen :-(.
Bei Windows 2k/XP usw ist es immer gleich (auf 4 Byte auffüllen).
Bei meiner Suse 9.0 am Laptop und Suse 9.2 am Desktop pc (x86_64 long modus)
ist es, wie ich es oben erwähnt habe.
char buf[20] ->0x14 ->0x20 -> 0x28 = 40 byte reserviert.
Am Anfang dachte ich das liegt an meinem athlon64 (abgesehen davon dass ich das ganze mit einem 64 bit und einem 32 bit kernel getestet habe). Verwirrt war ich erst, als ich die selben Ergebnisse mit meinem Laptop (Intel Centrino 1.8Ghz) erziehlt habe.
Ich vermute mal du hast nicht zufällig noch eine andere Distro zum testen installiert.
Btw. probier mal ein Programm nur mit einem char buf[18] und näher dich ab 18 Byte byteweise an den eip ran, bis du einen coredump bekommst und im gdb steht dass der Eip (wenn du nur mit a auffüllst) mit 0x61616161 überschrieben wird mit einem Aufruf àla:
Code:
perl -e 'print "a" x 26' | ./test
 
Ich hab LFS laufen, da koennte ich das ganze mal testen.

Nachtrag: Hab grad mal das Programm von RedWing kompiliert und getestet.
Das kommt dabei raus:
stack address of b =0xbfe9a918
stack address of test =0xbfe9a91e
stack address of a =0xbfe9a930
Bei mir sind es demnach auch 18 Bytes.
Kernel 2.6.17.7 (das System baut aber auf 2.6.16 auf), GlibC 2.3.6, GCC 4.1.0
 
Seltsam. Liegt entweder an einer alten Compiler version die ich habe.
Oder....Shice Suse 9.x:).
Wie gesagt, mich würd bei euch auch der abstand zwischen char buf[18] und ebp interessieren.

Wenn das wirklich so ist, müsste bei dir der Speicherbereich zwischen buf und ebp
in etwa so aussehen.
Code:
     0x61616161 0x61616161 0x61616161 0x61616161 0x61618980 (ebp) 0x00000408
und das würd mir eigentlich komisch vorkommen

im Windows ist das immer so in etwa

Code:
     0x61616161 0x61616161 0x61616161 0x61616161 0x00006161    0x89800408(ebp)
 
Hallo,

Nebuchadnezar hat gesagt.:
Wenn mit Framepointer der Beginn des Stacks gemeint ist ja.

Ja mit dem Framepointer mein ich den Zeiger auf den Anfang des aktuellen
Stackframe:
Ich kann leider nur von der M68K Architektur sprechen, denke aber das das bei
einem x86 der selbe Mechansimus sein sollte:
Beim M86K steht der Stackpointer immer im Adressregister A7 und der
Framepointer im Adressregister (man kann für den Framepointer auch ein
beliebiges andres Register wählen, aber Konvention is da glaube A6) A6:

Stack:
Code:
niedere Adressen    A7 ->   funktionslokale Parameter
                    A6 ->   A6.alt
                            Rücksprungadresse
                            aktuelle Parameter
                            ...
obere Adressen              A6.alt

Wenn der Stack dann wieder abgebaut wird, geschieht im westl. folgendes:

1.) Der Stackpointer A7 wird mit dem Wert des Framepointers A6 geladen (lokaler
Speicherplatz wird freigegeben)
2.) Der Framepointer A6 wird mit dem Wert des alten Framepointers der sich nun
hinter dem Wert der Adresse A7 versteckt restauriert und A7 wird um 4 erhöht
4.) Der Programcounter wird mit dem Wert der sich jetzt hinter A7 versteckt
geladen (Rücksprung)

Ich denke aber das dieser Mechanismus vom Compiler abhängig sein kann.

Dennis hat gesagt.:
Evtl. interessant koennten aber die Versionen von GlibC und GCC sein,

Code:
redwing@euklid:~ $ gcc --version
gcc (GCC) 4.0.4 20060730 (prerelease) (Debian 4.0.3-6)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE

Ich denke die Version der glibc ist hier unintressant.
Und wie gesagt das Programm läuft bei mir auf einem x86...

Ich werds gleich mal noch unter Windows ausprobieren....
@Nebuchadnezar Was hast du denn für einen Compiler verwendet?
Eventuell liegts aber auch an der 64 Bit Architektur...

Gruß,
RedWing
 
Das müsst eigentlich stimmen was du sagst.
Wenn du ein C Programm oder was auch immer disassemblierst schaut das so aus:
mov esp, ebp (glaub intel syntax - von rechts nach links)
Code:
push ebp
mov ebp,esp (intel syntax glaub ich ... von rechts nach links)
...

pop ebp
mov esp,ebp
 
Zurück