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

  • 0x264 Форматные строки

  • Параметр Тип вывода %dДесятичный%uБеззнаковый десятичный%xШестнадцатеричный 0x260 Возвращаемся к основам 63

  • Параметр Тип вывода

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


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

    eip 0xb7f076f4 0xb7f076f4
    (gdb) x/5i $eip
    0xb7f076f4 : mov esi,DWORD PTR [ebp+8]
    0xb7f076f7 : mov eax,DWORD PTR [ebp+12]
    0xb7f076fa : mov ecx,esi
    0xb7f076fc : sub ecx,eax
    0xb7f076fe : mov edx,eax
    (gdb) continue
    Continuing.
    Breakpoint 3, main () at char_array2.c:8 8 printf(str_a);
    (gdb) i r eip eip 0x80483d7 0x80483d7
    (gdb) x/5i $eip

    0x260 Возвращаемся к основам
    55
    0x80483d7 : lea eax,[ebp-40]
    0x80483da : mov DWORD PTR [esp],eax
    0x80483dd : call 0x80482d4 0x80483e2 : leave
    0x80483e3 : ret
    (gdb)
    Адрес, находящийся в EIP в средней точке останова, отличается от других, потому что код функции strcpy() находится в загруженной би- блиотеке. В средней точке отладчик отображает EIP для кода функции strcpy()
    , тогда как в двух других точках EIP указывает на код функ- ции main(). Хочу подчеркнуть, что EIP способен «переноситься» из основного кода в код strcpy() и обратно. При каждом вызове функции запись об этом сохраняется в структуре данных, называемой стеком.
    С помощью стекаEIP может возвращаться из длинных цепочек вызо- вов функций. В GDB есть команда bt (от backtrace), позволяющая про- следить цепочку вызовов, помещенную в стек. Ниже показано состоя- ние стека для каждой точки останова.
    (gdb) run
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /home/reader/booksrc/char_array2
    Error in re-setting breakpoint 4:
    Function “strcpy” not defined.
    Breakpoint 1, main () at char_array2.c:7 7 strcpy(str_a, “Hello, world!\n”);
    (gdb) bt
    #0 main () at char_array2.c:7
    (gdb) cont
    Continuing.
    Breakpoint 4, 0xb7f076f4 in strcpy () from /lib/tls/i686/cmov/libc.so.6
    (gdb) bt
    #0 0xb7f076f4 in strcpy () from /lib/tls/i686/cmov/libc.so.6
    #1 0x080483d7 in main () at char_array2.c:7
    (gdb) cont
    Continuing.
    Breakpoint 3, main () at char_array2.c:8 8 printf(str_a);
    (gdb) bt
    #0 main () at char_array2.c:8
    (gdb)
    В средней точке останова обратная трассировка стека показывает вы- зов strcpy(). Можно также заметить, что во время второго прогона адрес функции strcpy() немного изменился. Это результат действия защиты от эксплойтов, включаемой по умолчанию в ядро Linux начиная с вер- сии 2.6.11. Несколько позднее мы подробно рассмотрим эту защиту.

    56
    0x200 Программирование
    0x262 Целые числа со знаком, без знака,
    длинные и короткие
    По умолчанию числовые значения в C имеют знак, то есть могут быть как положительными, так и отрицательными. Напротив, беззнаковые значения не могут быть отрицательными. Поскольку все это хранится в памяти, все числовые значения должны храниться в двоичном виде, и двоичный вид беззнаковых значений понятнее всего. 32-разрядное беззнаковое целое может принимать значения от 0 (все разряды – нули) до 4 294 967 295 (все разряды – единицы). 32-разрядное целое со зна- ком содержит те же 32 бита и, следовательно, может быть одной из 2 32
    возможных комбинаций. Поэтому 32-разрядные целые находятся в ди- апазоне от −2 147 483 648 до 2 147 483 647. Фактически, один из раз- рядов служит флажком, сигнализирующим о том, какое это число: по- ложительное или отрицательное. Положительные значения со знаком имеют тот же вид, что и беззнаковые, но отрицательные хранятся ина- че – в так называемом дополнительном коде (two’s complement). Пред- ставление чисел вдополнительном коде удобно для работы двоичных сумматоров: если сложить отрицательную величину в дополнительном коде с положительным числом с такой же абсолютной величиной, то в результате получится 0. Чтобы получить запись в двоичном дополни- тельном коде, нужно записать число в двоичном виде, инвертировать все его разряды и прибавить к результату 1. Звучит странно, но дей- ствует и позволяет складывать отрицательные числа с положительны- ми с помощью простых двоичных сумматоров.
    Эту арифметику легко проверить с помощью pcalc – простого кальку- лятора для программистов, который показывает результаты в десятич- ном, шестнадцатеричном и двоичном форматах. Для простоты в приме- ре использованы 8-битные числа.
    reader@hacking:

    /booksrc $ pcalc 0y01001001 73 0x49 0y1001001
    reader@hacking:/booksrc $ pcalc 0y10110110 + 1 183 0xb7 0y10110111
    reader@hacking:/booksrc $ pcalc 0y01001001 + 0y10110111 256 0x100 0y100000000
    reader@hacking:/booksrc $
    Сначала мы видим, что двоичное значение 01001001 – это положитель- ное число 73. Затем инвертируем все разряды и прибавляем 1, чтобы получить дополнительный код для –73 (10110111). Сложив эти величи- ны, получим 0 в исходных 8 разрядах. Программа pcalc показывает ре- зультат 256, потому что не знает, что мы оперируем всего лишь с 8-бит- ными числами. В двоичном сумматоре этот бит переноса будет отбро- шен, поскольку выходит за границы памяти, отведенной переменной.
    Этот пример может немного прояснить, как действует механизм допол- нительного кода.

    0x260 Возвращаемся к основам
    57
    В C можно определить переменную как беззнаковую с помощью ключе- вого слова unsigned, помещаемого в объявлении перед именем перемен- ной. Беззнаковое целое объявляется как unsigned int.
    Кроме того, размер памяти, выделяемой числовой переменной, мож- но увеличить или уменьшить с помощью ключевых слов long и short.
    Факти ческий размер зависит от архитектуры, для которой компили- руется код. В языке C есть оператор sizeof(), умеющий определять раз- мер некоторых типов данных. Он действует как функция, принима- ющая на входе тип данных и возвращающая размер в целевой архитек- туре переменной, объявляемой с таким типом. Следующая програм- ма datatype_sizes.c исследует размер разных типов данных с помощью функции sizeof().
    datatype_sizes.c
    #include
    int main() {
    printf(“The ‘int’ data type is\t\t %d bytes\n”, sizeof(int));
    printf(“The ‘unsigned int’ data type is\t %d bytes\n”, sizeof(unsigned int));
    printf(“The ‘short int’ data type is\t %d bytes\n”, sizeof(short int));
    printf(“The ‘long int’ data type is\t %d bytes\n”, sizeof(long int));
    printf(“The ‘long long int’ data type is %d bytes\n”, sizeof(long long int));
    printf(“The ‘float’ data type is\t %d bytes\n”, sizeof(float));
    printf(“The ‘char’ data type is\t\t %d bytes\n”, sizeof(char));
    }
    В этом коде функция printf() используется несколько по-иному, чем прежде. Здесь применен так называемый спецификатор формата, что- бы показать значения, возвращаемые вызовами sizeof(). Подробнее о спецификаторах формата мы поговорим ниже, а здесь рассмотрим ре- зультат работы программы.
    reader@hacking:/booksrc $ gcc datatype_sizes.c reader@hacking:/booksrc $ ./a.out
    The ‘int’ data type is 4 bytes
    The ‘unsigned int’ data type is 4 bytes
    The ‘short int’ data type is 2 bytes
    The ‘long int’ data type is 4 bytes
    The ‘long long int’ data type is 8 bytes
    The ‘float’ data type is 4 bytes
    The ‘char’ data type is 1 bytes reader@hacking:/booksrc $
    Как уже отмечалось, в архитектуре x86 знаковые и беззнаковые це- лые числа занимают 4 байта. Число с плавающей точкой тоже зани- мает 4 байта, а символьному типу нужен всего 1 байт. Ключевые сло-

    58
    0x200 Программирование ва long и short можно
    1
    использовать и с переменными с плавающей точ- кой, чтобы увеличить или сократить их размер.
    0x263 Указатели
    Регистр EIP представляет собой указатель следующей команды, по- скольку содержит ее адрес. Идея указателей используется и в языке
    C. Поскольку физическую память реально перемещать нельзя, при- ходится копировать содержащуюся в ней информацию. Копирование больших блоков памяти, чтобы с ними могли работать разные функ- ции и в разных местах, требует очень больших накладных расходов.
    Это дорого обходится и с точки зрения памяти, потому что перед копи- рованием нужно сохранить или выделить память для нового экземпля- ра данных. Решение данной проблемы дают указатели. Гораздо проще не копировать большой участок памяти, а передать адрес его начала.
    Указатели объявляются и используются в C так же, как переменные прочих типов. В архитектуре x86 применяется 32-разрядная адреса- ция, поэтому указатели имеют размер 32 бита (4 байта). Указатели объявляются с помощью звездочки (*), помещаемой перед именем пе- ременной. В результате указатель оказывается не переменной неко- торого типа, а средством указания на данные этого типа. Программа
    pointer.c демонстрирует применение указателей с типом данных char, имеющих размер 1 байт.
    pointer.c
    #include
    #include
    int main() {
    char str_a[20]; // Массив из 20 символов char *pointer; // Указатель массива символов char *pointer2; // Другой указатель strcpy(str_a, “Hello, world!\n”);
    pointer = str_a; // Установим первый указатель на начало массива.
    printf(pointer);
    pointer2 = pointer + 2; // Установим второй указатель на 2 байта дальше.
    printf(pointer2); // Вывод.
    strcpy(pointer2, “y you guys!\n”); // Копируем в то же место.
    printf(pointer); // Еще один вывод.
    }
    Как сказано в комментариях, первый указатель устанавливается на начало массива символов. Такая ссылка на массив фактически пред-
    1
    Автор ошибается. Можно использовать ключевое слово double (8 байт) или long double
    (16 байт, иногда 80 бит). – Прим. перев.

    0x260 Возвращаемся к основам
    59
    ставляет собой указатель. Выше этот буфер передавался функциям printf()
    и strcpy() именно как указатель. Второму указателю присва- ивается адрес из первого плюс 2, а затем выполняется печать некото- рых данных.
    reader@hacking:/booksrc $ gcc -o pointer pointer.c reader@hacking:/booksrc $ ./pointer
    Hello, world!
    llo, world!
    Hey you guys!
    reader@hacking:/booksrc $
    Посмотрим эту программу с помощью GDB. Программа компилирует- ся, и задается точка останова в 10-й строке исходного кода. В резуль- тате останов произойдет после того, как строка «Hello, world!\n” будет скопирована в буфер str_a и переменная-указатель будет установлена на его начало.
    reader@hacking:/booksrc $ gcc -g -o pointer pointer.c reader@hacking:/booksrc $ gdb -q ./pointer
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) list
    1 #include
    2 #include
    3 4 int main() {
    5 char str_a[20]; // Массив символов из 20 элементов
    6 char *pointer; // Указатель массива символов
    7 char *pointer2; // Другой указатель
    8 9 strcpy(str_a, “Hello, world!\n”);
    10 pointer = str_a; // Установим первый указатель на начало массива.
    11 printf(pointer);
    12 13 pointer2 = pointer + 2; // Установим второй указатель на 2 байта дальше.
    14 printf(pointer2); // Вывод.
    15 strcpy(pointer2, “y you guys!\n”); // Копируем в то же место.
    16 printf(pointer); // Еще один вывод.
    17 }
    (gdb) break 11
    Breakpoint 1 at 0x80483dd: file pointer.c, line 11.
    (gdb) run
    Starting program: /home/reader/booksrc/pointer
    Breakpoint 1, main () at pointer.c:11 11 printf(pointer);
    (gdb) x/xw pointer
    0xbffff7e0: 0x6c6c6548
    (gdb) x/s pointer
    0xbffff7e0: “Hello, world!\n”
    (gdb)

    60
    0x200 Программирование
    Если рассмотреть указатель как строку, то заметно, что это наша стро- ка, находящаяся по адресу 0xbffff7e0. Не забываем, что в переменной- указателе хранится не сама строка, а только ее адрес 0xbffff7e0.
    Чтобы увидеть фактические данные, хранящиеся в переменной- указателе, нужно воспользоваться оператором адреса. Оператор адреса является унарным, то есть применяется к единственному аргументу. Он представляет собой всего лишь амперсанд (&), помещаемый перед име- нем переменной. При его использовании возвращается адрес этой пере- менной, а не ее значение. Этот оператор есть и в GDB, и в языке C.
    (gdb) x/xw &pointer
    0xbffff7dc: 0xbffff7e0
    (gdb) print &pointer
    $1 = (char **) 0xbffff7dc
    (gdb) print pointer
    $2 = 0xbffff7e0 “Hello, world!\n”
    (gdb)
    Мы видим, что переменная pointer находится в памяти по адресу
    0xbffff7dc и хранит адрес 0xbffff7e0.
    Оператор адреса часто используется вместе с указателями, поскольку указатели хранят адреса памяти. Программа addressof.c демонстриру- ет, как адрес целочисленной переменной помещается в указатель. Со- ответствующая строка кода выделена полужирным.
    addressof.c
    #include
    int main() {
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // записать адрес int_var в int_ptr
    }
    Сама программа ничего не выводит, но можно сообразить, что в ней происходит, даже не запуская отладчик.
    reader@hacking:/booksrc $ gcc -g addressof.c reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) list
    1 #include
    2 3 int main() {
    4 int int_var = 5;
    5 int *int_ptr;
    6 7 int_ptr = &int_var; // Записать адрес переменной int_var в int_ptr
    8 }

    0x260 Возвращаемся к основам
    61
    (gdb) break 8
    Breakpoint 1 at 0x8048361: file addressof.c, line 8.
    (gdb) run
    Starting program: /home/reader/booksrc/a.out
    Breakpoint 1, main () at addressof.c:8 8 }
    (gdb) print int_var
    $1 = 5
    (gdb) print &int_var
    $2 = (int *) 0xbffff804
    (gdb) print int_ptr
    $3 = (int *) 0xbffff804
    (gdb) print &int_ptr
    $4 = (int **) 0xbffff800
    (gdb)
    Как обычно, устанавливается точка останова и программа выполняет- ся в отладчике. К этому моменту выполнена большая часть програм- мы. Первая команда print выводит значение переменной int_var, а вто- рая – ее адрес, полученный с помощью оператора адреса. Очередные две команды print показывают, что в int_ptr содержится адрес int_var, а за- одно и адрес int_ptr.
    Для работы с указателями есть еще один унарный оператор, называ- емый оператором разыменования (dereference). Он возвращает дан- ные, находящиеся по адресу, содержащемуся в указателе, а не сам этот адрес. Он выглядит как звездочка перед именем переменной, подобно объявлению указателя. Оператор разыменования тоже есть и в GDB, и в C. В GDB с его помощью можно получить целое число, на которое указывает int_ptr.
    (gdb) print *int_ptr
    $5 = 5
    Несколько дополнив код листинга addressof2.c, продемонстрируем все эти понятия. В добавленных функциях printf() используются пара- метры формата, о которых я расскажу в следующем разделе. Здесь же ограничимся рассмотрением результата работы программы.
    addressof2.c
    #include
    int main() {
    int int_var = 5;
    int *int_ptr;
    int_ptr = &int_var; // Записать адрес int_var в int_ptr.
    printf(“int_ptr = 0x%08x\n”, int_ptr);
    printf(“&int_ptr = 0x%08x\n”, &int_ptr);
    printf(“*int_ptr = 0x%08x\n\n”, *int_ptr);
    printf(“int_var is located at 0x%08x and contains %d\n”, &int_var, int_var);

    62
    0x200 Программирование printf(“int_ptr is located at 0x%08x, contains 0x%08x, and points to %d\n\n”, &int_ptr, int_ptr, *int_ptr);
    }
    В результате компиляции и запуска addressof2.c получим следующий вывод:
    reader@hacking:/booksrc $ gcc addressof2.c reader@hacking:/booksrc $ ./a.out int_ptr = 0xbffff834
    &int_ptr = 0xbffff830
    *int_ptr = 0x00000005
    int_var is located at 0xbffff834 and contains 5
    int_ptr is located at 0xbffff830, contains 0xbffff834, and points to 5
    reader@hacking:/booksrc $
    Применяя унарные операторы к указателям, можно представлять себе, что оператор адреса перемещает нас назад, а оператор разымено- вания – вперед по направлению указателя.
    0x264 Форматные строки
    Функция printf() позволяет не только выводить фиксированные стро- ки. Применяя в ней форматные строки, можно печатать переменные в различных форматах. Форматная строка – это строка символов со специальными управляющими последовательностями, или escape-
    последовательностями, указывающими функции на необходимость разместить на месте этих управляющих последовательностей перемен- ные в заданном формате.
    Формально в предыдущих программах, вызывавших функцию printf()
    , строка «Hello, world!\n” являлась форматной строкой, одна- ко в ней не было специальных управляющих последовательностей.
    Escape-последовательности называют также параметрами формати-
    рования, и для каждого такого параметра, встретившегося в формат- ной строке, функция должна принять дополнительный аргумент. Каж- дый параметр форматирования начинается с символа процента (%) и со- держит односимвольное сокращение, весьма похожее на символы фор- матирования, применяемые в команде examine отладчика GDB.
    Параметр_Тип_вывода_%dДесятичный%uБеззнаковый_десятичный%xШестнадцатеричный_0x260_Возвращаемся_к_основам_63'>Параметр
    Тип вывода
    %d
    Десятичный
    %u
    Беззнаковый десятичный
    %x
    Шестнадцатеричный

    0x260 Возвращаемся к основам
    63
    Все указанные параметры форматирования получают данные в виде значений, а не указателей. Однако есть параметры форматирования, которые предполагают применение указателей, например:
    Параметр
    Тип вывода
    %s
    Строка
    %n
    Количество выведенных байтов
    Параметр форматирования %s предполагает передачу адреса памяти; он выводит данные начиная с этого адреса, пока не встретится нулевой байт. Параметр форматирования %n является уникальным, поскольку он записывает данные. Он также предполагает передачу адреса памя- ти, по которому записывает количество выведенных к данному момен- ту байтов.
    Сейчас нас интересуют только параметры форматирования, применя- емые для вывода данных. Программа fmt_strings.c содержит примеры использования различных параметров форматирования.
    fmt_strings.c
    #include
    int main() {
    char string[10];
    int A = -73;
    unsigned int B = 31337;
    strcpy(string, “sample”);
    // Пример вывода с разными форматными строками printf(“[A] Dec: %d, Hex: %x, Unsigned: %u\n”, A, A, A);
    printf(“[B] Dec: %d, Hex: %x, Unsigned: %u\n”, B, B, B);
    printf(“[field width on B] 3: ‘%3u’, 10: ‘%10u’, ‘%08u’\n”, B, B, B);
    printf(“[string] %s Address %08x\n”, string, string);
    // Пример унарного оператора адреса (разыменования) и форматной строки %x printf(“variable A is at address: %08x\n”, &A);
    }
    В этом коде для каждого параметра в форматной строке функции printf()
    передается в качестве аргумента дополнительная переменная.
    В последнем вызове printf() участвует аргумент &A, который передает адрес переменной A. Ниже приведен результат компиляции и выполне- ния программы.
    reader@hacking:/booksrc $ gcc -o fmt_strings fmt_strings.c reader@hacking:/booksrc $ ./fmt_strings
    [A] Dec: -73, Hex: ffffffb7, Unsigned: 4294967223
    [B] Dec: 31337, Hex: 7a69, Unsigned: 31337

    1   2   3   4   5   6   7   8   9   10   ...   51


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