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

  • 0x265 Приведение типа

  • int_pointer = (int *) ((char *) int_pointer + 1);

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


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

    64
    0x200 Программирование
    [field width on B] 3: ‘31337’, 10: ‘ 31337’, ‘00031337’
    [string] sample Address bffff870
    variable A is at address: bffff86c reader@hacking:

    /booksrc $
    Первые два вызова printf() демонстрируют вывод переменных A и B с помощью разных параметров форматирования. Поскольку в каждой строке три параметра форматирования, переменные A и B нужно пере- дать по три раза каждую. Формат %d позволяет выводить отрицатель- ные значения, а формат %u – нет, поскольку предполагает беззнаковые величины.
    Когда переменная A выводится с параметром форматирования %u, ее значение оказывается очень большим. Это происходит потому, что от- рицательное число A хранится в дополнительном коде, а параметр фор- матирования пытается вывести его так, как если бы это было беззнако- вое значение. Поскольку дополнительный код образуется инвертиро- ванием всех битов и прибавлением единицы, прежние нулевые стар- шие биты превращаются в единицы.
    Третья строка примера, начинающаяся с метки [field width on B], по- казывает, как в параметре форматирования задается ширина поля. Это просто целое число, задающее минимальную ширину поля для данно- го параметра. Однако максимальную ширину поля оно не определяет: если выводимое значение больше, чем допускает ширина поля, она бу- дет увеличена. Так происходит, например, когда задана ширина поля
    3, а для вывода данных нужно 5 байт. Если задать ширину поля 10, то перед данными будет выведено 5 пробелов. Кроме того, если ширина поля начинается с 0, поле будет дополнено нулями. Например, при за- данной ширине поля 08 выводится 00031337.
    Четвертая строка, начинающаяся с метки [string], показывает приме- нение параметра форматирования %s. Напомню, что переменная стро- ка фактически является указателем, содержащим адрес строки, что прекрасно, поскольку параметр форматирования %s предполагает пе- редачу данных по ссылке.
    Последняя строка показывает адрес переменной A, полученный с помо- щью унарного оператора адреса. Он выводится в виде восьми шестнад- цатеричных цифр с дописанными нолями.
    Из этих примеров видно, что для десятичных чисел нужно применять формат %d, для целых без знака – %u, а для шестнадцатеричных – %x.
    Минимальную ширину поля можно задать с помощью числа, следу- ющего сразу за знаком процента, а если размер поля начинается с 0, оно будет дополнено незначащими нулями. С помощью параметра %s можно вывести строку, для чего в качестве аргумента передается адрес этой строки. Пока все ясно.
    Строки форматирования применяются в целом семействе стандартных функций ввода/вывода, включая scanf(), которая похожа на printf(),

    0x260 Возвращаемся к основам
    65
    но служит для ввода, а не вывода. Важное отличие scanf() состоит в том, что все ее аргументы являются указателями, поэтому ей нужно передавать не сами переменные, а их адреса. Это можно сделать с по- мощью переменных-указателей или применения унарного оператора адреса к обычным переменным. Программа input.c иллюстрирует это.
    input.c
    #include
    #include
    int main() {
    char message[10];
    int count, i;
    strcpy(message, “Hello, world!”);
    printf(“Repeat how many times? “);
    scanf(“%d”, &count);
    for(i=0; i < count; i++)
    printf(“%3d - %s\n”, i, message);
    }
    В программе input.c функция scanf() служит для установки значения переменной count. Ниже демонстрируется работа этой программы.
    reader@hacking:/booksrc $ gcc -o input input.c reader@hacking:/booksrc $ ./input
    Repeat how many times? 3 0 - Hello, world!
    1 - Hello, world!
    2 - Hello, world!
    reader@hacking:/booksrc $ ./input
    Repeat how many times? 12 0 - Hello, world!
    1 - Hello, world!
    2 - Hello, world!
    3 - Hello, world!
    4 - Hello, world!
    5 - Hello, world!
    6 - Hello, world!
    7 - Hello, world!
    8 - Hello, world!
    9 - Hello, world!
    10 - Hello, world!
    11 - Hello, world!
    reader@hacking:/booksrc $
    Форматные строки используются весьма часто, поэтому полезно хо- рошо их изучить. Кроме того, умея выводить значения переменных, можно отлаживать программу, не прибегая к отладчику. Возможность

    66
    0x200 Программирование мгновенно видеть реакцию на свои действия очень важна в процессе обучения хакера, а такие простые вещи, как вывод значения перемен- ной, открывают большие возможности для исследования программ.
    0x265 Приведение типа
    Приведение типа (typecasting) – это способ временно изменить тип дан- ных, хранящихся в переменной, на другой, отличный от ее первона- чального объявления. Приведение типа переменной есть указание ком- пилятору обрабатывать ее так, как если бы она имела заданный новый тип, но только на время выполнения текущей операции. Синтаксис приведения типа имеет следующий вид:
    (новый_тип_данных) переменная
    Приведением типа можно воспользоваться, когда нужно обработать целые числа и числа с плавающей точкой, что демонстрирует програм- ма typecasting.c.
    typecasting.c
    #include
    int main() {
    int a, b;
    float c, d;
    a = 13;
    b = 5;
    c = a / b; // Деление целых чисел.
    d = (float) a / (float) b; // Деление целых чисел,
    // приведенных к типу float.
    printf(“[integers]\t a = %d\t b = %d\n”, a, b);
    printf(“[floats]\t c = %f\t d = %f\n”, c, d);
    }
    Ниже приведен результат компилирования и прогона программы
    typecasting.c.
    reader@hacking:/booksrc $ gcc typecasting.c reader@hacking:/booksrc $ ./a.out
    [integers] a = 13 b = 5
    [floats] c = 2.000000 d = 2.600000
    reader@hacking:/booksrc $
    Как уже говорилось, деление целого числа 13 на 5 даст неверный окру- гленный результат 2, даже если записывать его в переменную с плава- ющей точкой. Однако если привести эти целочисленные переменные к типу с плавающей точкой, они будут обрабатываться как действи- тельные числа, и мы получим правильный результат 2,6.

    0x260 Возвращаемся к основам
    67
    Это наглядный пример, но еще более ярко приведение типа проявля- ет себя при работе с переменными-указателями. Несмотря на то что указатель – это всего лишь адрес памяти, компилятор C требует ука- зывать тип данных для всех указателей. Отчасти это вызвано стремле- нием предотвратить ошибки программистов. Указатель на целый тип должен указывать на целочисленные данные, а указатель на символь- ный тип – только на символьные. Другая причина – арифметика ука- зателей. Целое число занимает четыре байта, а символ – всего один.
    Программа pointer_types.c иллюстрирует и объясняет эти понятия.
    В ее коде используется параметр форматирования %p, задающий вывод адреса памяти. Это сокращенное обозначение формата для вывода ука- зателей, практически равносильного формату 0x%08x.
    pointer_types.c
    #include
    int main() {
    int i;
    char char_array[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
    int int_array[5] = {1, 2, 3, 4, 5};
    char *char_pointer;
    int *int_pointer;
    char_pointer = char_array;
    int_pointer = int_array;
    for(i=0; i < 5; i++) { // Обойти массив целых с помощью int_pointer.
    printf(“[integer pointer] points to %p, which contains the integer %d\n”, int_pointer, *int_pointer);
    int_pointer = int_pointer + 1;
    }
    for(i=0; i < 5; i++) { // Обойти массив символов с помощью char_pointer.
    printf(“[char pointer] points to %p, which contains the char ‘%c’\n”,
    char_pointer, *char_pointer);
    char_pointer = char_pointer + 1;
    }
    }
    Этот код определяет в памяти два массива: один с целочисленными данными, а другой – с символьными. Определены также два указате- ля – на данные целого типа и на символьные, – каждый из них содер- жит адрес начала соответствующего массива. Два отдельных цикла for осуществляют обход массивов с применением арифметики указа- телей для перенацеливания указателей на очередные элементы масси- вов. Обратите внимание: когда целые числа и символы выводятся в ци-

    68
    0x200 Программирование клах с помощью параметров форматирования %d и %c, соответствующие аргументы printf() должны разыменовывать переменные-указатели.
    Для этого применяется унарный оператор разыменования *.
    reader@hacking:/booksrc $ gcc pointer_types.c reader@hacking:/booksrc $ ./a.out
    [integer pointer] points to 0xbffff7f0, which contains the integer 1
    [integer pointer] points to 0xbffff7f4, which contains the integer 2
    [integer pointer] points to 0xbffff7f8, which contains the integer 3
    [integer pointer] points to 0xbffff7fc, which contains the integer 4
    [integer pointer] points to 0xbffff800, which contains the integer 5
    [char pointer] points to 0xbffff810, which contains the char ‘a’
    [char pointer] points to 0xbffff811, which contains the char ‘b’
    [char pointer] points to 0xbffff812, which contains the char ‘c’
    [char pointer] points to 0xbffff813, which contains the char ‘d’
    [char pointer] points to 0xbffff814, which contains the char ‘e’
    reader@hacking:/booksrc $
    Несмотря на то что к int_pointer и char_pointer добавляется в циклах одно и то же число 1, компилятор увеличивает хранящиеся в указате- лях адреса по-разному. Тип char занимает 1 байт, поэтому указатель на следующий символ будет больше на 1 байт. А тип integer занимает
    4 байта, поэтому указатель на следующее целое число будет больше на
    4 байта.
    В программе pointer_types2.c указатели преобразованы так, чтобы ука- затель на целый тип int_pointer указывал на символьные данные и на- оборот.
    pointer_types2.c
    #include
    int main() {
    int i;
    char char_array[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
    int int_array[5] = {1, 2, 3, 4, 5};
    char *char_pointer;
    int *int_pointer;
    char_pointer = int_array; // Теперь char_pointer и int_pointer указывают int_pointer = char_array; // на несовместимые типы данных for(i=0; i < 5; i++) { // Обойти массив целых с помощью int_pointer.
    printf(“[integer pointer] points to %p, which contains the integer %d\n”,
    int_pointer, *int_pointer);
    int_pointer = int_pointer + 1;
    }

    0x260 Возвращаемся к основам
    69
    for(i=0; i < 5; i++) { // Обойти массив символов с помощью char_pointer.
    printf(“[char pointer] points to %p, which contains the integer %d\n”,
    char_pointer, *char_pointer);
    char_pointer = char_pointer + 1;
    }
    }
    Ниже показаны предупредительные сообщения, выдаваемые компи- лятором.
    reader@hacking:/booksrc $ gcc pointer_types2.c pointer_types2.c: In function `main’:
    pointer_types2.c:12: warning: assignment from incompatible pointer type pointer_types2.c:13: warning: assignment from incompatible pointer type reader@hacking:/booksrc $
    Пытаясь помешать появлению ошибки в программе, компилятор преду преждает, что указатели указывают на несовместимые типы дан- ных. Но тип указателя волнует только компилятор и, возможно, про- граммиста. В скомпилированном коде указатель – всего лишь адрес памяти, поэтому компилятор скомпилирует код, даже если указатель указывает на несовместимый тип данных, – он лишь предупреждает программиста, что результат работы программы может оказаться не- ожиданным.
    reader@hacking:/booksrc $ ./a.out
    [integer pointer] points to 0xbffff810, which contains the char ‘a’
    [integer pointer] points to 0xbffff814, which contains the char ‘e’
    [integer pointer] points to 0xbffff818, which contains the char ‘8’
    [integer pointer] points to 0xbffff81c, which contains the char ‘
    [integer pointer] points to 0xbffff820, which contains the char ‘?’
    [char pointer] points to 0xbffff7f0, which contains the integer 1
    [char pointer] points to 0xbffff7f1, which contains the integer 0
    [char pointer] points to 0xbffff7f2, which contains the integer 0
    [char pointer] points to 0xbffff7f3, which contains the integer 0
    [char pointer] points to 0xbffff7f4, which contains the integer 2
    reader@hacking:/booksrc $
    Несмотря на то что int_pointer указывает на символьные данные, со- держащие 5 байт данных, он сохраняет тип указателя на целое. Это означает, что прибавление к указателю 1 будет увеличивать адрес на 4.
    Аналогично адрес в char_pointer будет каждый раз увеличиваться все- го на 1, и проход через 20 байт целочисленных данных (пять 4-байт- ных чисел) будет осуществляться по одному байту. Просматривая
    4-байтные целые числа по одному байту, мы снова убеждаемся, что данные хранятся в порядке «сначала младший байт». 4-байтное число
    0x00000001
    хранится в памяти как байты 0x01, 0x00, 0x00, 0x00.
    Мы будем сталкиваться с подобными ситуациями, когда указатель указывает на данные несовместимого с ним типа. Поскольку тип ука- зателя определяет размер данных, на которые он указывает, нужно следить за правильностью типа. Как видно из приведенного ниже ли-

    70
    0x200 Программирование стинга pointer_types3.c, приведение типа позволяет изменить тип пере- менной «на лету».
    pointer_types3.c
    #include
    int main() {
    int i;
    char char_array[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
    int int_array[5] = {1, 2, 3, 4, 5};
    char *char_pointer;
    int *int_pointer;
    char_pointer = (char *) int_array; // Приведение к типу int_pointer = (int *) char_array; // данных указателя.
    for(i=0; i < 5; i++) { // Обойти массив целых с помощью int_pointer.
    printf(“[integer pointer] points to %p, which contains the integer %d\n”,
    int_pointer, *int_pointer);
    int_pointer = (int *) ((char *) int_pointer + 1);
    }
    for(i=0; i < 5; i++) { // Обойти массив символов с помощью char_pointer.
    printf(“[char pointer] points to %p, which contains the integer %d\n”,
    char_pointer, *char_pointer);
    char_pointer = (char *) ((int *) char_pointer + 1);
    }
    }
    В этом коде при начальном задании значений указателей происходит приведение типа данных к типу данных указателя. В результате ком- пилятор C перестанет сообщать о конфликте типов данных, но ариф- метика указателей все равно будет действовать некорректно. Чтобы исправить положение, при добавлении 1 к указателю нужно сначала привести его к нужному типу данных, чтобы адрес увеличился на пра- вильную величину. После этого нужно снова вернуть первоначальный тип данных указателя. Не очень изящно, но действует.
    reader@hacking:/booksrc $ gcc pointer_types3.c reader@hacking:/booksrc $ ./a.out
    [integer pointer] points to 0xbffff810, which contains the char ‘a’
    [integer pointer] points to 0xbffff811, which contains the char ‘b’
    [integer pointer] points to 0xbffff812, which contains the char ‘c’
    [integer pointer] points to 0xbffff813, which contains the char ‘d’
    [integer pointer] points to 0xbffff814, which contains the char ‘e’
    [char pointer] points to 0xbffff7f0, which contains the integer 1
    [char pointer] points to 0xbffff7f4, which contains the integer 2
    [char pointer] points to 0xbffff7f8, which contains the integer 3

    0x260 Возвращаемся к основам
    71
    [char pointer] points to 0xbffff7fc, which contains the integer 4
    [char pointer] points to 0xbffff800, which contains the integer 5
    reader@hacking:/booksrc $
    Конечно, гораздо проще сразу выбрать для указателя правильный тип данных, однако иногда желательно иметь некий общий указатель без типа. В C такой указатель без типа определяется с помощью ключево- го слова void.
    В ходе экспериментов с такими указателями быстро выясняются не- которые их особенности. Во-первых, нельзя разыменовать указатель, если у него нет типа. Чтобы извлечь из памяти данные, адрес которых содержится в указателе, компилятору нужно знать тип этих данных.
    Во-вторых, указатель void нужно привести к какому-нибудь типу, что- бы выполнять с ним арифметические действия. Это достаточно очевид- ные ограничения, из которых следует, что основная задача указателя без типа – просто хранить адрес памяти.
    Программу pointer_types3.c можно модифицировать так, чтобы в ней использовался один указатель void, приводимый к нужному типу при каждом его использовании. Компилятор знает, что у указателя void нет типа, и позволяет присвоить ему любой указатель без приведения типа. Однако при разыменовании указателя void необходимо выпол- нить приведение его типа. Все это показано в листинге pointer_types4.c, где используется указатель void.
    pointer_types4.c
    #include
    int main() {
    int i;
    char char_array[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
    int int_array[5] = {1, 2, 3, 4, 5};
    void *void_pointer;
    void_pointer = (void *) char_array;
    for(i=0; i < 5; i++) { // Обход массива char.
    printf(“[char pointer] points to %p, which contains the char ‘%c’\n”,
    void_pointer, *((char *) void_pointer));
    void_pointer = (void *) ((char *) void_pointer + 1);
    }
    void_pointer = (void *) int_array;
    for(i=0; i < 5; i++) { // Обход массива int.
    printf(“[integer pointer] points to %p, which contains the integer %d\n”,
    void_pointer, *((int *) void_pointer));

    72
    0x200 Программирование void_pointer = (void *) ((int *) void_pointer + 1);
    }
    }
    В результате компиляции и выполнения программы pointer_types4.c получаем:
    reader@hacking:/booksrc $ gcc pointer_types4.c reader@hacking:/booksrc $ ./a.out
    [char pointer] points to 0xbffff810, which contains the char ‘a’
    [char pointer] points to 0xbffff811, which contains the char ‘b’
    [char pointer] points to 0xbffff812, which contains the char ‘c’
    [char pointer] points to 0xbffff813, which contains the char ‘d’
    [char pointer] points to 0xbffff814, which contains the char ‘e’
    [integer pointer] points to 0xbffff7f0, which contains the integer 1
    [integer pointer] points to 0xbffff7f4, which contains the integer 2
    [integer pointer] points to 0xbffff7f8, which contains the integer 3
    [integer pointer] points to 0xbffff7fc, which contains the integer 4
    [integer pointer] points to 0xbffff800, which contains the integer 5
    reader@hacking:/booksrc $
    Компиляция и вывод программ pointer_types4.c и pointer_types3.c вы- глядят одинаково. Указатель void всего лишь хранит адрес памяти, а приведение типа сообщает компилятору, какой тип указателя дол- жен использоваться в каждом случае.
    Поскольку нужный тип устанавливается путем приведения типа, ука- затель void – это всего лишь адрес памяти. Если тип данных задается с помощью приведения типа, значит в роли указателя void можно ис- пользовать любой тип, размер которого позволяет хранить четыре бай- та данных. В программе pointer_types5.c для хранения адреса исполь- зуется беззнаковое целое.
    1   ...   4   5   6   7   8   9   10   11   ...   51


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