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

  • hacky_nonpointer = hacky_nonpointer + sizeof(char); } 0x260 Возвращаемся к основам 73

  • hacky_nonpointer = hacky_nonpointer + sizeof(int);

  • 0x266 Аргументы командной строки

  • 0x267 Область видимости переменных

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


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

    pointer_types5.c
    #include
    int main() {
    int i;
    char char_array[5] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’};
    int int_array[5] = {1, 2, 3, 4, 5};
    unsigned int hacky_nonpointer;
    hacky_nonpointer = (unsigned int) char_array;
    for(i=0; i < 5; i++) { // Обход массива char.
    printf(“[hacky_nonpointer] points to %p, which contains the char ‘%c’\n”,
    hacky_nonpointer, *((char *) hacky_nonpointer));
    hacky_nonpointer = hacky_nonpointer + sizeof(char);
    }

    0x260 Возвращаемся к основам
    73
    hacky_nonpointer = (unsigned int) int_array;
    for(i=0; i < 5; i++) { // Обход массива int.
    printf(“[hacky_nonpointer] points to %p, which contains the integer %d\n”,
    hacky_nonpointer, *((int *) hacky_nonpointer));
    hacky_nonpointer = hacky_nonpointer + sizeof(int);
    }
    }
    Это уже почти хакинг, но поскольку при присваивании и разыменова- нии целочисленное значение приводится к нужному типу, конечный результат остается тем же самым.
    Обратите внимание: вместо многократного приведения типа беззнако- вого целого для выполнения арифметических действий над указателя- ми используется функция sizeof() и обычная арифметика, что прино- сит тот же самый результат.
    reader@hacking:

    /booksrc $ gcc pointer_types5.c reader@hacking:/booksrc $ ./a.out
    [hacky_nonpointer] points to 0xbffff810, which contains the char ‘a’
    [hacky_nonpointer] points to 0xbffff811, which contains the char ‘b’
    [hacky_nonpointer] points to 0xbffff812, which contains the char ‘c’
    [hacky_nonpointer] points to 0xbffff813, which contains the char ‘d’
    [hacky_nonpointer] points to 0xbffff814, which contains the char ‘e’
    [hacky_nonpointer] points to 0xbffff7f0, which contains the integer 1
    [hacky_nonpointer] points to 0xbffff7f4, which contains the integer 2
    [hacky_nonpointer] points to 0xbffff7f8, which contains the integer 3
    [hacky_nonpointer] points to 0xbffff7fc, which contains the integer 4
    [hacky_nonpointer] points to 0xbffff800, which contains the integer 5
    reader@hacking:/booksrc $
    Работая с переменными в C, нужно помнить, что их тип интересен толь- ко компилятору. После того как программа скомпилирована, перемен- ные представляют собой всего лишь адреса памяти. Это означает, что переменные одного типа можно заставить вести себя как переменные другого типа, заставив компилятор выполнить соответствующее при- ведение типа.
    0x266 Аргументы командной строки
    Программы, работающие в текстовом режиме, часто получают вход- ные данные в виде аргументов командной строки. В отличие от ввода с помощью scanf(), аргументы командной строки не требуют взаимо- действия с пользователем после запуска программы. Часто такой метод ввода оказывается эффективнее и удобнее.
    В C доступ к аргументам командной строки можно получить через функцию main(), указав для нее два дополнительных аргумента: целое число и указатель на массив строк. Целое число задает количество ар-

    74
    0x200 Программирование гументов командной строки, а массив строк хранит все эти аргументы.
    Это иллюстрирует программа commandline.c.
    commandline.c
    #include
    int main(int arg_count, char *arg_list[]) {
    int i;
    printf(“There were %d arguments provided:\n”, arg_count);
    for(i=0; i < arg_count; i++)
    printf(“argument #%d\t-\t%s\n”, i, arg_list[i]);
    }
    reader@hacking:/booksrc $ gcc -o commandline commandline.c reader@hacking:/booksrc $ ./commandline
    There were 1 arguments provided:
    argument #0 - ./commandline reader@hacking:/booksrc $ ./commandline this is a test
    There were 5 arguments provided:
    argument #0 - ./commandline argument #1 - this argument #2 - is argument #3 - a argument #4 - test reader@hacking:/booksrc $
    Нулевой аргумент массива всегда содержит имя исполняемой програм- мы, а остальные аргументы из массива (часто называемого вектором
    аргументов) представляют собой строки с аргументами, переданными программе.
    Иногда программе нужно использовать аргумент командной строки как число, а не как строку. При этом аргументы все равно передаются как строки, но есть стандартные функции преобразования. В отличие от простого приведения типа, эти функции могут действительно преоб- разовать массив символов, которым записано число, в настоящее число.
    Чаще всего используется функция atoi() (от ASCII to integer), преобра- зующая ASCII-код в целое. Она принимает в качестве аргумента указа- тель на строку, а возвращает целое число, представленное этой строкой.
    Ее работу можно изучить на примере программы convert.c.
    convert.c
    #include
    void usage(char *program_name) {
    printf(“Usage: %s <# of times to repeat>\n”, program_name);
    exit(1);
    }
    int main(int argc, char *argv[]) {

    0x260 Возвращаемся к основам
    75
    int i, count;
    if(argc < 3) // Если аргументов меньше 3, вывести сообщение usage(argv[0]); // о том, как вызывать программу, и завершить работу.
    count = atoi(argv[2]); // Преобразовать 2-й аргумент в целое число.
    printf(“Repeating %d times..\n”, count);
    for(i=0; i < count; i++)
    printf(“%3d - %s\n”, i, argv[1]); // Вывести 1-й аргумент.
    }
    Ниже приведен результат компиляции и выполнения программы
    convert.c.
    reader@hacking:/booksrc $ gcc convert.c reader@hacking:/booksrc $ ./a.out
    Usage: ./a.out <# of times to repeat>
    reader@hacking:/booksrc $ ./a.out ‘Hello, world!’ 3
    Repeating 3 times..
    0 - Hello, world!
    1 - Hello, world!
    2 - Hello, world!
    reader@hacking:/booksrc $
    Прежде чем этот код будет работать со строками, оператор if проверя- ет наличие не менее трех аргументов. Если программа попытается об- ратиться к несуществующему или запрещенному для доступа адресу памяти, она аварийно завершится. В программах на C необходимо про- верять возникновение таких ситуаций и обрабатывать их. Если заком- ментировать оператор if, в котором проверяется ошибка, можно иссле- довать, что происходит при нарушении правильного доступа к памяти.
    Это показано в программе convert2.c.
    convert2.c
    #include
    void usage(char *program_name) {
    printf(“Usage: %s <# of times to repeat>\n”, program_name);
    exit(1);
    }
    int main(int argc, char *argv[]) {
    int i, count;
    // if(argc < 3) // Если аргументов меньше 3, вывести сообщение
    // usage(argv[0]); // о том, как вызывать программу,
    // и завершить работу.
    count = atoi(argv[2]); // Преобразовать 2-й аргумент в целое число.
    printf(“Repeating %d times..\n”, count);

    76
    0x200 Программирование for(i=0; i < count; i++)
    printf(“%3d - %s\n”, i, argv[1]); // Вывести 1-й аргумент.
    }
    Ниже приведен результат компиляции и выполнения convert2.c.
    reader@hacking:/booksrc $ gcc convert2.c reader@hacking:/booksrc $ ./a.out test
    Segmentation fault (core dumped)
    reader@hacking:/booksrc $
    Если не передать программе достаточное количество аргументов, она все равно попытается обратиться к элементам массива аргументов, даже если их нет. В результате программа аварийно завершится из-за ошибки сегментирования.
    Память разбита на сегменты (о которых будет говориться ниже), и неко- торые адреса памяти выходят за границы сегментов, доступ к которым разрешен программе. При попытке обращения по адресу, выходящему за границы, программа аварийно завершается с сообщением об ошиб-
    ке сегментации (segmentation fault). Это событие можно дополнитель- но исследовать с помощью GDB.
    reader@hacking:/booksrc $ gcc -g convert2.c reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) run test
    Starting program: /home/reader/booksrc/a.out test
    Program received signal SIGSEGV, Segmentation fault.
    0xb7ec819b in ?? () from /lib/tls/i686/cmov/libc.so.6
    (gdb) where
    #0 0xb7ec819b in ?? () from /lib/tls/i686/cmov/libc.so.6
    #1 0xb800183c in ?? ()
    #2 0x00000000 in ?? ()
    (gdb) break main
    Breakpoint 1 at 0x8048419: file convert2.c, line 14.
    (gdb) run test
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /home/reader/booksrc/a.out test
    Breakpoint 1, main (argc=2, argv=0xbffff894) at convert2.c:14 14 count = atoi(argv[2]); // Преобразовать 2-й аргумент
    // в целое число.
    (gdb) cont
    Continuing.
    Program received signal SIGSEGV, Segmentation fault.
    0xb7ec819b in ?? () from /lib/tls/i686/cmov/libc.so.6
    (gdb) x/3xw 0xbffff894 0xbffff894: 0xbffff9b3 0xbffff9ce 0x00000000

    0x260 Возвращаемся к основам
    77
    (gdb) x/s 0xbffff9b3 0xbffff9b3: «/home/reader/booksrc/a.out»
    (gdb) x/s 0xbffff9ce
    0xbffff9ce: “test”
    (gdb) x/s 0x00000000 0x0:

    (gdb) quit
    The program is running. Exit anyway? (y or n) y reader@hacking:/booksrc $
    Программа запускается в GDB с одним аргументом командной строки test
    , что приводит к ее аварийному завершению. Команда where ино- гда отображает полезную обратную трассировку стека, однако в дан- ном случае авария слишком повредила стек. Мы поставили точку оста- нова на main и заново запустили программу, чтобы узнать значение вектора аргументов (выделено полужирным). Поскольку вектор аргу- ментов является указателем на список строк, фактически он является указателем на список указателей. Проверив с помощью команды x/3xw первые три адреса памяти, хранящиеся по адресу вектора аргументов, мы видим, что они сами являются указателями на строки. Первый из них – это нулевой аргумент, второй – аргумент test, а третий – ноль, который выходит за границы разрешенной памяти. При попытке обра- титься по этому адресу программа аварийно завершается с сообщением об ошибке сегментации.
    0x267 Область видимости переменных
    Еще одно интересное понятие, касающееся памяти в C, это область ви- димости, или контекст – а именно контекст переменных внутри функ- ций. У каждой функции есть собственный набор локальных перемен- ных, независимых от всего остального. Фактически, если одна функ- ция вызывается многократно, то каждый вызов происходит в отдель- ном контексте. Быстро убедиться в этом можно на примере функции printf()
    с форматными строками в программе scope.c.
    scope.c
    #include
    void func3() {
    int i = 11;
    printf(“\t\t\t[in func3] i = %d\n”, i);
    }
    void func2() {
    int i = 7;
    printf(“\t\t[in func2] i = %d\n”, i);
    func3();
    printf(“\t\t[back in func2] i = %d\n”, i);
    }

    78
    0x200 Программирование void func1() {
    int i = 5;
    printf(“\t[in func1] i = %d\n”, i);
    func2();
    printf(“\t[back in func1] i = %d\n”, i);
    }
    int main() {
    int i = 3;
    printf(“[in main] i = %d\n”, i);
    func1();
    printf(“[back in main] i = %d\n”, i);
    }
    Работа этой простой программы демонстрирует выполнение вложен- ных вызовов функций.
    reader@hacking:/booksrc $ gcc scope.c reader@hacking:/booksrc $ ./a.out
    [in main] i = 3
    [in func1] i = 5
    [in func2] i = 7
    [in func3] i = 11
    [back in func2] i = 7
    [back in func1] i = 5
    [back in main] i = 3
    reader@hacking:/booksrc $
    В каждой функции переменной i присваивается новое значение и вы- водится на экран. Обратите внимание: внутри функции main() значе- ние переменной i равно 3 даже после вызова func1(), в которой значе- ние i равно 5. Аналогично в функции func1() переменная i сохраня- ет значение 5 даже после вызова func2(), где она получает значение 7, и так далее. Правильным будет считать, что в каждом вызове функции действует своя версия переменной i.
    Переменные могут также иметь глобальную область видимости, что означает их действие во всех функциях. Глобальными становятся пе- ременные, объявленные в начале кода вне тела какой-либо функции.
    В программе scope2.c переменная j объявлена как глобальная и полу- чает значение 42. Чтение и запись этой переменной можно осущест- влять в любой функции, и эти изменения отражаются на всех функ- циях.
    scope2.c
    #include
    int j = 42; // j объявлена как глобальная переменная void func3() {
    int i = 11, j = 999; // Здесь j - локальная переменная функции func3().

    0x260 Возвращаемся к основам
    79
    printf(“\t\t\t[in func3] i = %d, j = %d\n”, i, j);
    }
    void func2() {
    int i = 7;
    printf(“\t\t[in func2] i = %d, j = %d\n”, i, j);
    printf(“\t\t[in func2] setting j = 1337\n”);
    j = 1337; // Запись в j func3();
    printf(“\t\t[back in func2] i = %d, j = %d\n”, i, j);
    }
    void func1() {
    int i = 5;
    printf(“\t[in func1] i = %d, j = %d\n”, i, j);
    func2();
    printf(“\t[back in func1] i = %d, j = %d\n”, i, j);
    }
    int main() {
    int i = 3;
    printf(“[in main] i = %d, j = %d\n”, i, j);
    func1();
    printf(“[back in main] i = %d, j = %d\n”, i, j);
    }
    Результат компиляции и выполнения scope2.c:
    reader@hacking:/booksrc $ gcc scope2.c reader@hacking:/booksrc $ ./a.out
    [in main] i = 3, j = 42
    [in func1] i = 5, j = 42
    [in func2] i = 7, j = 42
    [in func2] setting j = 1337
    [in func3] i = 11, j = 999
    [back in func2] i = 7, j = 1337
    [back in func1] i = 5, j = 1337
    [back in main] i = 3, j = 1337
    reader@hacking:/booksrc $
    Мы видим, что глобальной переменной j в функции func2() присваива- ется значение, которое используется во всех функциях, кроме func3(), где есть собственная переменная с именем j. В подобном случае ком- пилятор предпочитает использовать локальную переменную. Если пе- ременные имеют одинаковые имена, может возникнуть путаница, но нужно помнить, что все это связано с памятью. Глобальная перемен- ная j хранится в памяти, доступ к которой есть у каждой функции. Ло- кальные переменные отдельной функции хранятся в особом участке памяти, несмотря на совпадение их имен с именами глобальных пере- менных. Если вывести адреса этих переменных, можно лучше понять,

    80
    0x200 Программирование что происходит. Например, в коде программы scope3.c адреса перемен- ных выводятся с помощью унарного оператора адреса.
    scope3.c
    #include
    int j = 42; // j – глобальная переменная.
    void func3() {
    int i = 11, j = 999; // Здесь j – локальная переменная func3()
    printf(“\t\t\t[in func3] i @ 0x%08x = %d\n”, &i, i);
    printf(“\t\t\t[in func3] j @ 0x%08x = %d\n”, &j, j);
    }
    void func2() {
    int i = 7;
    printf(“\t\t[in func2] i @ 0x%08x = %d\n”, &i, i);
    printf(“\t\t[in func2] j @ 0x%08x = %d\n”, &j, j);
    printf(“\t\t[in func2] setting j = 1337\n”);
    j = 1337; // Writing to j func3();
    printf(“\t\t[back in func2] i @ 0x%08x = %d\n”, &i, i);
    printf(“\t\t[back in func2] j @ 0x%08x = %d\n”, &j, j);
    }
    void func1() {
    int i = 5;
    printf(“\t[in func1] i @ 0x%08x = %d\n”, &i, i);
    printf(“\t[in func1] j @ 0x%08x = %d\n”, &j, j);
    func2();
    printf(“\t[back in func1] i @ 0x%08x = %d\n”, &i, i);
    printf(“\t[back in func1] j @ 0x%08x = %d\n”, &j, j);
    }
    int main() {
    int i = 3;
    printf(“[in main] i @ 0x%08x = %d\n”, &i, i);
    printf(“[in main] j @ 0x%08x = %d\n”, &j, j);
    func1();
    printf(“[back in main] i @ 0x%08x = %d\n”, &i, i);
    printf(“[back in main] j @ 0x%08x = %d\n”, &j, j);
    }
    Результат компиляции и выполнения scope3.c:
    reader@hacking:/booksrc $ gcc scope3.c reader@hacking:/booksrc $ ./a.out
    [in main] i @ 0xbffff834 = 3
    [in main] j @ 0x08049988 = 42
    [in func1] i @ 0xbffff814 = 5
    [in func1] j @ 0x08049988 = 42

    0x260 Возвращаемся к основам
    81
    [in func2] i @ 0xbffff7f4 = 7
    [in func2] j @ 0x08049988 = 42
    [in func2] setting j = 1337
    [in func3] i @ 0xbffff7d4 = 11
    [in func3] j @ 0xbffff7d0 = 999
    [back in func2] i @ 0xbffff7f4 = 7
    [back in func2] j @ 0x08049988 = 1337
    [back in func1] i @ 0xbffff814 = 5
    [back in func1] j @ 0x08049988 = 1337
    [back in main] i @ 0xbffff834 = 3
    [back in main] j @ 0x08049988 = 1337
    reader@hacking:/booksrc $
    Очевидно, что переменная j, используемая в func3(), отличается от j
    в других функциях. Переменная j из функции func3() хранится по адресу 0xbffff7d0, а переменная j из остальных функций – по адресу
    0x08049988
    . Обратите также внимание на то, что у каждой функции есть
    своя переменная i, – адреса этих переменных разные.
    В следующем листинге видно, что GDB останавливает выполнение в точке останова на функции func3(). После этого команда backtrace по- казывает все вызовы функции, записанные в стеке.
    reader@hacking:/booksrc $ gcc -g scope3.c reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) list 1 1 #include
    2 3 int j = 42; // j – глобальная переменная.
    4 5 void func3() {
    6 int i = 11, j = 999; // Здесь j – локальная переменная func3().
    7 printf(“\t\t\t[in func3] i @ 0x%08x = %d\n”, &i, i);
    8 printf(“\t\t\t[in func3] j @ 0x%08x = %d\n”, &j, j);
    9 }
    10
    (gdb) break 7
    Breakpoint 1 at 0x8048388: file scope3.c, line 7.
    (gdb) run
    Starting program: /home/reader/booksrc/a.out
    [in main] i @ 0xbffff804 = 3
    [in main] j @ 0x08049988 = 42
    [in func1] i @ 0xbffff7e4 = 5
    [in func1] j @ 0x08049988 = 42
    [in func2] i @ 0xbffff7c4 = 7
    [in func2] j @ 0x08049988 = 42
    [in func2] setting j = 1337
    Breakpoint 1, func3 () at scope3.c:7 7 printf(“\t\t\t[in func3] i @ 0x%08x = %d\n”, &i, i);
    (gdb) bt

    82
    0x200 Программирование
    #0 func3 () at scope3.c:7
    #1 0x0804841d in func2 () at scope3.c:17
    #2 0x0804849f in func1 () at scope3.c:26
    #3 0x0804852b in main () at scope3.c:35
    (gdb)
    Обратная трассировка показывает также вложенные вызовы функций по записям, хранящимся на стеке. При каждом обращении к функ- ции в стек помещается запись, называемая кадром стека (stack frame).
    Каждая строка в обратной трассировке соответствует кадру стека. Каж- дый кадр стека также хранит локальные переменные для данного кон- текста. Локальные переменные кадров стека можно показать в GDB, если добавить в команду backtrace слово full (полная).
    (gdb) bt full
    #0 func3 () at scope3.c:7
    i = 11
    j = 999
    #1 0x0804841d in func2 () at scope3.c:17
    i = 7
    #2 0x0804849f in func1 () at scope3.c:26
    i = 5
    #3 0x0804852b in main () at scope3.c:35
    i = 3
    (gdb)
    Полная обратная трассировка подтверждает, что локальная перемен- ная j есть только в контексте func3(). В контекстах других функций ис- пользуется глобальная версия переменной j.
    Помимо глобальных, есть статические переменные, определяемые до- бавлением в объявление переменной ключевого слова static. Подобно глобальным, статические переменные сохраняют свое значение меж- ду вызовами функции; однако они похожи и на локальные, поскольку остаются локальными внутри контекста конкретной функции. Особым и уникальным свойством статических переменных является то, что они инициализируются только один раз. Все сказанное иллюстри рует код
    static.c.
    1   ...   5   6   7   8   9   10   11   12   ...   51


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