Главная страница
Навигация по странице:

  • Рис. 2.2.

  • 0x272 Работа с кучей

  • 0x273 Функция malloc() с контролем ошибок

  • 0x281 Доступ к файлам

  • Хакинг. Хакинг__искусство_эксплоита_2_е_469663841. Книга дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах


    Скачать 2.5 Mb.
    НазваниеКнига дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах
    АнкорХакинг
    Дата16.06.2022
    Размер2.5 Mb.
    Формат файлаpdf
    Имя файлаХакинг__искусство_эксплоита_2_е_469663841.pdf
    ТипКнига
    #595131
    страница11 из 51
    1   ...   7   8   9   10   11   12   13   14   ...   51

    0x00000001 0x00000002 0x00000003 0x00000004
    (gdb)
    В конце показан кадр стека. Внизу кадра видны четыре аргумента функции 5, а сразу над ними – адрес возврата 4. Выше располагает- ся сохраненный указатель 0xbffff808 3, представляющий собой содер- жимое EBP в предыдущем кадре стека. Остальная память отведена для локальных переменных flag и buffer. Расчет их адресов относительно
    EBP точно показывает, где они находятся в стеке. Память переменной flag отмечена 2, а переменной buffer – 1. Оставшееся в стеке место – просто для заполнения.
    По завершении выполнения весь кадр выталкивается из стека, а в EIP помещается адрес возврата, чтобы программа могла продолжить свое выполнение. Если внутри функции происходит вызов другой функ- ции, в стек проталкивается еще один кадр, и так далее. В конце рабо- ты каждой функции ее кадр стека выталкивается, чтобы можно было продолжить выполнение предыдущей функции. Такой режим рабо- ты оправдывает организацию этого сегмента памяти в виде структуры данных типа FILO.
    Различные сегменты памяти организованы в указанном порядке – от младших адресов памяти к старшим. Так как нумерованный список привычнее читать сверху вниз, младшие адреса памяти располагаются вверху. В некоторых книгах принят противоположный порядок, что может сбивать с толку, поэтому в данной книге меньшие адреса памя- ти всегда оказываются вверху. В большинстве отладчиков память так- же отображается в этом стиле: младшие адреса памяти находятся ввер- ху, а старшие – внизу.
    Поскольку размер кучи и стека устанавливается динамически, эти два сегмента растут в противоположных направлениях, навстречу друг другу. Это сокращает непроизводительный расход памяти, позволяя увеличивать стек, когда куча невелика, и наоборот (рис. 2.2).

    92
    0x200 Программирование сегмент text сегмент data сегмент bss сегмент heap сегмент stack
    Стек растет в направлении младших адресов
    Куча растет в направлении старших адресов
    Младшие адреса
    Старшие адреса
    Рис. 2.2. Расположение сегментов в памяти
    0x271 Сегменты памяти в C
    В C и других компилируемых языках скомпилированный код помеща- ется в сегмент текста, а переменные располагаются в других сегмен- тах. Сегмент, в который попадает переменная, зависит от того, как она определена. Переменные, определенные вне каких-либо функ- ций, считаются глобальными. Кроме того, с помощью ключевого сло- ва static любую переменную можно определить как статическую. Если статическая или глобальная переменная описана с начальным значе- нием, она помещается в сегмент данных, в противном случае – в сег- мент bss. Память в сегменте кучи нужно сначала выделить с помощью функции malloc(). Обычно обращение к памяти в куче происходит че- рез указатели. Остальные переменные функции хранятся в сегменте стека. В стеке может быть много кадров, поэтому помещаемые в стек переменные могут сохранять свою уникальность в различных функци- ональных контекстах. Программа memory_segments.c поможет разо- браться с этими концепциями в C.
    memory_segments.c
    #include
    int global_var;
    int global_initialized_var = 5;
    void function() { // Это демонстрационная функция int stack_var; // Переменная с таким же именем есть в main().
    printf(“the function’s stack_var is at address 0x%08x\n”, &stack_var);
    }

    0x270 Сегментация памяти
    93
    int main() {
    int stack_var; // Переменная с таким же именем есть в function()
    static int static_initialized_var = 5;
    static int static_var;
    int *heap_var_ptr;
    heap_var_ptr = (int *) malloc(4);
    // Эти переменные находятся в сегменте данных.
    printf(“global_initialized_var is at address 0x%08x\n”,
    &global_initialized_var);
    printf(“static_initialized_var is at address 0x%08x\n\n”,
    &static_initialized_var);
    // Эти переменные находятся в сегменте bss.
    printf(“static_var is at address 0x%08x\n”, &static_var);
    printf(“global_var is at address 0x%08x\n\n”, &global_var);
    // Эта переменная находится в сегменте кучи.
    printf(“heap_var is at address 0x%08x\n\n”, heap_var_ptr);
    // Эти переменные находятся в сегменте стека printf(“stack_var is at address 0x%08x\n”, &stack_var);
    function();
    }
    Работу этого кода легко понять благодаря содержательным именам пе- ременных. Глобальные и статические переменные объявлены, как опи- сано выше, кроме того, добавлены инициализированные переменные.
    Переменная stack_var объявлена как в main(), так и в function(), чтобы продемонстрировать действие функционального контекста. Перемен- ная heap объявлена как указатель на целое и будет указывать на адрес памяти в сегменте кучи. Функция malloc() предназначена для выделе- ния четырех байтов в куче. Поскольку выделяемая память может быть использована под любые данные, функция malloc() возвращает указа- тель на void, который нужно привести к указателю на целое.
    reader@hacking:

    /booksrc $ gcc memory_segments.c reader@hacking:/booksrc $ ./a.out global_initialized_var is at address 0x080497ec static_initialized_var is at address 0x080497f0
    static_var is at address 0x080497f8
    global_var is at address 0x080497fc heap_var is at address 0x0804a008
    stack_var is at address 0xbffff834
    the function’s stack_var is at address 0xbffff814
    reader@hacking:/booksrc $

    94
    0x200 Программирование
    Первые две инициализируемые переменные располагаются по самым младшим адресам, так как они находятся в сегменте данных. Следу- ющие две переменные, static_var и global_var, хранятся в сегменте bss, потому что они не инициализируются. Их адреса несколько больше, чем у предыдущих переменных, потому что сегмент bss расположен ниже сегмента данных. После компиляции размер обоих этих сегмен- тов не будет изменяться, поэтому потери памяти здесь незначительны, а адреса находятся не слишком далеко друг от друга.
    Переменная heap хранится в памяти, выделенной в сегменте кучи, рас- полагающемся сразу после сегмента bss. Напомню, что размер этого сегмента не фиксирован и впоследствии может динамически увеличи- ваться. Наконец, у последних двух переменных stack_var очень боль- шие адреса в памяти, потому что они размещаются в сегменте стека.
    Размер стека тоже не фиксирован, но он начинается внизу и увеличи- вается вверх по направлению к сегменту кучи. Благодаря этому оба сег- мента динамические, и память не расходуется понапрасну. Первая пе- ременная stack_var в контексте функции main() хранится в кадре сте- ка внутри сегмента стека. У второй переменной stack_var в function() свой отдельный уникальный контекст, поэтому эта переменная хранит- ся в другом кадре стека внутри сегмента стека. Когда ближе к концу программы выполняется вызов function(), создается новый кадр стека, чтобы (среди прочего) хранить stack_var для контекста function(). По- скольку с каждым новым кадром стек растет в сторону сегмента кучи, адрес второй переменной stack_var (0xbffff814) меньше, чем адрес пер- вой переменной stack_var (0xbffff834), находящейся в контексте main().
    0x272 Работа с кучей
    Другие сегменты памяти используются, когда переменная объявлена соответствующим образом, и не требуют особых усилий – в отличие от сегмента кучи. Как уже отмечалось, выделение памяти в куче происхо- дит с помощью функции malloc(). Она принимает единственный аргу- мент, который задает количество памяти, которое требуется выделить в сегменте кучи, и возвращает указатель void, содержащий адрес нача- ла выделенной памяти. Если по какой-либо причине malloc() не смогла выделить память, она возвращает указатель NULL со значением 0. Пар- ная функция, которая освобождает память, называется free(). В каче- стве единственного аргумента она принимает указатель и освобожда- ет память в куче, после чего ту можно снова использовать. Работу этих довольно простых функций иллюстрирует программа heap_example.c.
    heap_example.c
    #include
    #include
    #include int main(int argc, char *argv[]) {

    0x270 Сегментация памяти
    95
    char *char_ptr; // Указатель на символьный тип int *int_ptr; // Указатель на целый тип int mem_size;
    if (argc < 2) // Если нет аргументов командной строки,
    mem_size = 50; // используется 50 – значение по умолчанию.
    else mem_size = atoi(argv[1]);
    printf(“\t[+] allocating %d bytes of memory on the heap for char_ptr\n”, mem_size);
    char_ptr = (char *) malloc(mem_size); // Выделение памяти в куче if(char_ptr == NULL) { // Проверка ошибки – сбоя malloc()
    fprintf(stderr, “Error: could not allocate heap memory.\n”);
    exit(-1);
    }
    strcpy(char_ptr, “This is memory is located on the heap.”);
    printf(“char_ptr (%p) --> ‘%s’\n”, char_ptr, char_ptr);
    printf(“\t[+] allocating 12 bytes of memory on the heap for int_ptr\n”);
    int_ptr = (int *) malloc(12); // Еще раз выделяем память в куче if(int_ptr == NULL) { // Проверка ошибки – сбоя malloc() fails fprintf(stderr, “Error: could not allocate heap memory.\n”);
    exit(-1);
    }
    *int_ptr = 31337; // Поместить 31337 по адресу, содержащемуся в int_ptr.
    printf(“int_ptr (%p) --> %d\n”, int_ptr, *int_ptr);
    printf(“\t[-] freeing char_ptr’s heap memory...\n”);
    free(char_ptr); // Освободить память в куче printf(“\t[+] allocating another 15 bytes for char_ptr\n”);
    char_ptr = (char *) malloc(15); // Еще раз выделяем память в куче if(char_ptr == NULL) { // Проверка ошибки – сбоя malloc()
    fprintf(stderr, “Error: could not allocate heap memory.\n”);
    exit(-1);
    }
    strcpy(char_ptr, “new memory”);
    printf(“char_ptr (%p) --> ‘%s’\n”, char_ptr, char_ptr);
    printf(“\t[-] freeing int_ptr’s heap memory...\n”);
    free(int_ptr); // Освободить память в куче printf(“\t[-] freeing char_ptr’s heap memory...\n”);
    free(char_ptr); // Освободить еще один блок памяти в куче
    }

    96
    0x200 Программирование
    Размер первого выделяемого блока памяти программа берет из аргу- мента командной строки или по умолчанию принимает значение 50.
    Затем с помощью функций malloc() и free() производится выделение и освобождение памяти в куче. Многочисленные операторы printf() служат для контроля происходящего во время работы программы. Так как malloc() не знает, для данных какого типа предназначена выделя- емая ей память, она возвращает указатель void, который нужно приве- сти к нужному типу. После каждого обращения к malloc() выполняет- ся проверка ошибки, чтобы выяснить, успешно ли прошло выделение памяти. Если выделить память не удалось и возвращен указатель NULL, с помощью fprintf() на стандартное устройство выводится сообщение об ошибке и программа завершается. Функция fprintf() очень похожа на printf(), но в качестве первого аргумента ей передается stderr как указатель стандартного файлового потока для отображения ошибок.
    Об этой функции будет подробнее рассказано позже, здесь она нужна только для правильного отображения ошибок. В остальном програм- ма достаточно проста.
    reader@hacking:/booksrc $ gcc -o heap_example heap_example.c reader@hacking:/booksrc $ ./heap_example
    [+] allocating 50 bytes of memory on the heap for char_ptr char_ptr (0x804a008) --> ‘This is memory is located on the heap.’
    [+] allocating 12 bytes of memory on the heap for int_ptr int_ptr (0x804a040) --> 31337
    [-] freeing char_ptr’s heap memory...
    [+] allocating another 15 bytes for char_ptr char_ptr (0x804a050) --> ‘new memory’
    [-] freeing int_ptr’s heap memory...
    [-] freeing char_ptr’s heap memory...
    reader@hacking:/booksrc $
    По этому выводу видно, что каждый новый блок получает все больший адрес памяти в куче. Несмотря на то что первые 50 байт были освобож- дены, запрос дополнительных 15 байт возвращает адрес после 12 байт, выделенных для int_ptr. Такой режим работы определяется функция- ми выделения памяти в куче, и можно продолжить его изучение, задав другой размер первоначально выделяемой памяти.
    reader@hacking:/booksrc $ ./heap_example 100
    [+] allocating 100 bytes of memory on the heap for char_ptr char_ptr (0x804a008) --> ‘This is memory is located on the heap.’
    [+] allocating 12 bytes of memory on the heap for int_ptr int_ptr (0x804a070) --> 31337
    [-] freeing char_ptr’s heap memory...
    [+] allocating another 15 bytes for char_ptr char_ptr (0x804a008) --> ‘new memory’
    [-] freeing int_ptr’s heap memory...
    [-] freeing char_ptr’s heap memory...
    reader@hacking:/booksrc $

    0x270 Сегментация памяти
    97
    Если выделить, а потом освободить более крупный блок памяти, то по- следние 15 байт будут выделены из этой освобожденной памяти. Поэкс- периментировав с разными значениями, можно точно выяснить, в ка- ких случаях функция выделения памяти начинает повторно исполь- зовать освободившуюся память. Часто с помощью обычных команд printf()
    и небольших экспериментов можно многое узнать о том, как устроена ваша система.
    0x273 Функция malloc() с контролем ошибок
    В программе heap_example.c есть несколько проверок ошибки после вы- зова malloc(). Даже если malloc() всегда срабатывает успешно, в коде на
    C необходимо обрабатывать все случаи, когда может произойти ошибка при выделении памяти. Но если вызовов malloc() много, код для провер- ки ошибки приходится использовать несколько раз. От этого програм- ма приобретает неприглядный вид, а внесение изменений в код провер- ки или добавление обращений к malloc() становится обременительным.
    Поскольку код проверки ошибок фактически одинаков для всех обраще- ний к malloc(), вместо группы одинаковых команд в разных местах весь- ма эффективно использовать функцию. Примером служит программа
    errorchecked_heap.c.
    errorchecked_heap.c
    #include
    #include
    #include
    void *errorchecked_malloc(unsigned int); // Прототип функции
    // errorchecked_malloc()
    int main(int argc, char *argv[]) {
    char *char_ptr; // Указатель на символьный тип int *int_ptr; // Указатель на целый тип int mem_size;
    if (argc < 2) // Если нет аргументов командной строки,
    mem_size = 50; // используется 50 – значение по умолчанию.
    else mem_size = atoi(argv[1]);
    printf(“\t[+] allocating %d bytes of memory on the heap for char_ptr\n”, mem_size);
    char_ptr = (char *) errorchecked_malloc(mem_size); // Выделение памяти
    // в куче strcpy(char_ptr, “This is memory is located on the heap.”);
    printf(“char_ptr (%p) --> ‘%s’\n”, char_ptr, char_ptr);
    printf(“\t[+] allocating 12 bytes of memory on the heap for int_ptr\n”);

    98
    0x200 Программирование int_ptr = (int *) errorchecked_malloc(12); // Еще раз выделяем память
    // в куче
    *int_ptr = 31337; // Поместить 31337 по адресу, содержащемуся в int_ptr.
    printf(“int_ptr (%p) --> %d\n”, int_ptr, *int_ptr);
    printf(“\t[-] freeing char_ptr’s heap memory...\n”);
    free(char_ptr); // Освободить память в куче printf(“\t[+] allocating another 15 bytes for char_ptr\n”);
    char_ptr = (char *) errorchecked_malloc(15); // Еще раз выделяем память
    // в куче strcpy(char_ptr, “new memory”);
    printf(“char_ptr (%p) --> ‘%s’\n”, char_ptr, char_ptr);
    printf(“\t[-] freeing int_ptr’s heap memory...\n”);
    free(int_ptr); // Освободить память в куче printf(“\t[-] freeing char_ptr’s heap memory...\n”);
    free(char_ptr); // Освободить еще один блок памяти в куче
    }
    void *errorchecked_malloc(unsigned int size) { // Функция malloc()
    // с контролем ошибок void *ptr;
    ptr = malloc(size);
    if(ptr == NULL) {
    fprintf(stderr, “Error: could not allocate heap memory.\n”);
    exit(-1);
    }
    return ptr;
    }
    Программа errorchecked_heap.c в принципе идентична предыдущей
    heap_example.c, за исключением того, что выделение памяти в куче и проверка ошибок собраны в одну функцию. Первая строка кода (void
    *errorchecked_malloc(unsigned int);
    ) представляет собой прототип функ- ции. Благодаря ему компилятор знает, что есть функция с именем errorchecked_malloc()
    , которая принимает один аргумент беззнакового целого типа и возвращает указатель void. Действительный код функ- ции может находиться в любом месте, у нас он расположен после функ- ции main(). Собственно функция весьма проста: она получает размер об- ласти памяти, которую нужно выделить, и пытается зарезервировать эту память с помощью malloc(). Если выделить память не удается, код проверки ошибки выводит сообщение и завершает программу, в против- ном случае возвращается указатель на выделенную в куче область па- мяти. Таким образом, эту специальную функцию errorchecked_malloc() можно использовать вместо обычной malloc(), избавившись от необхо- димости вставлять код проверки ошибки после каждого обращения

    0x280 Опираясь на основы
    99
    к ней. Это наглядно демонстрирует пользу программирования с помо- щью функций.
    0x280 Опираясь на основы
    После того как вы разберетесь с основными понятиями программиро- вания на C, все остальное достаточно легко. Основу мощи C составляет использование функций. Если убрать функции из любой предыдущей программы, останутся только очень простые операторы.
    0x281 Доступ к файлам
    В C применяются два основных способа работы с файлами: через де- скрипторы файла и файловые потоки. Дескрипторы файла использу- ются группой функций низкоуровневого ввода/вывода, а файловые по-
    токи (filestreams) представляют собой буферизованный ввод/вывод более высокого уровня, построенный на функциях низкого уровня.
    Некоторые считают, что программировать с применением функций файловых потоков проще, однако дескрипторы файла обеспечивают большую непосредственность. В данной книге нас будут интересовать функции низкоуровневого ввода/вывода, использующие дескрипторы файла.
    Штрих-код на обороте этой книги представляет некоторое число. Каж- дой книге, которая есть в магазине, соответствует свое число, по этому числу кассир получает сведения о книге из базы данных. Аналогичным образом дескриптор файла – это число, с помощью которого можно об- ращаться к открытым файлам. Есть четыре стандартные функции, ис- пользующие дескрипторы файла: open(), close(), read() и write(). Все они в случае возникновения ошибки возвращают –1. Функция open() открывает файл для чтения и/или записи и возвращает дескриптор файла. Этот дескриптор является целым числом, соответствующим только одному открытому файлу. Дескриптор файла передается как аргумент другим функциям в качестве указателя на открытый файл.
    У функции close() дескриптор файла – единственный аргумент. Для функций read() и write() аргументами являются дескриптор файла, указатель на данные, которые нужно считать или записать, и количе- ство байт, которые нужно записать или считать по этому адресу. Аргу- менты функции open() – указатель на имя файла, который нужно от- крыть, и ряд флагов, описывающих метод доступа к файлу. Об этих флагах далее будет рассказано подробно, а сейчас мы рассмотрим про- стую программу simplenote.c для записи заметок, в которой использу- ются дескрипторы файла. Программа принимает сообщение, содержа- щееся в аргументе командной строки, и дописывает его в конец фай- ла /tmp/notes. В ней используется несколько функций, в том числе уже знакомая функция выделения памяти в куче с проверкой ошиб- ки. Другие функции служат для вывода сообщения о правилах вызова

    1   ...   7   8   9   10   11   12   13   14   ...   51


    написать администратору сайта