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

  • 0x08048357 : push ebp 0x08048358 : mov ebp,esp 0x0804835a : sub esp,0x18 0x0804835d : and esp,0xfffffff0 88

  • 0x08048344 : push ebp 0x08048345 : mov ebp,esp 0x08048347 : sub esp,0x28

  • 0x08048367 : mov DWORD PTR [esp+12],0x4 0x0804836f : mov DWORD PTR [esp+8],0x3 0x08048377 : mov DWORD PTR [esp+4],0x2

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


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

    static.c
    #include
    void function() { // Пример функции с собственным контекстом int var = 5;
    static int static_var = 5; // Инициализация статической переменной printf(“\t[in function] var = %d\n”, var);
    printf(“\t[in function] static_var = %d\n”, static_var);
    var++; // Прибавить 1 к var.
    static_var++; // Прибавить 1 к static_var.
    }

    0x260 Возвращаемся к основам
    83
    int main() { // Функция main с собственным контекстом int i;
    static int static_var = 1337; // Другая статическая переменная
    // в другом контексте for(i=0; i < 5; i++) { // Повторить 5 раз printf(“[in main] static_var = %d\n”, static_var);
    function(); // Вызвать функцию.
    }
    }
    Переменная static_var объявлена статической в двух местах: в контек- сте main() и в контексте function(). Поскольку статические переменные являются локальными в контексте конкретной функции, можно дать им одинаковые имена, но в действительности они будут представлять два разных адреса в памяти. function просто выводит значения этих двух переменных в своем контексте и добавляет к обеим 1. Скомпили- ровав и выполнив эту программу, мы увидим разницу между статиче- скими и не статическими переменными.
    reader@hacking:

    /booksrc $ gcc static.c reader@hacking:/booksrc $ ./a.out
    [in main] static_var = 1337
    [in function] var = 5
    [in function] static_var = 5
    [in main] static_var = 1337
    [in function] var = 5
    [in function] static_var = 6
    [in main] static_var = 1337
    [in function] var = 5
    [in function] static_var = 7
    [in main] static_var = 1337
    [in function] var = 5
    [in function] static_var = 8
    [in main] static_var = 1337
    [in function] var = 5
    [in function] static_var = 9
    reader@hacking:/booksrc $
    Обратите внимание: static_var сохраняет свое значение между после- довательными вызовами function(). Это происходит потому, что стати- ческие переменные сохраняют свои значения, и потому, что их ини- циализация выполняется только один раз. Кроме того, поскольку ста- тические переменные являются локальными в контексте конкрет- ной функции, static_var в контексте main() всегда сохраняет значение
    1337.
    И снова, выведя адреса этих переменных с помощью оператора адреса, мы получим лучшее представление о происходящем. Возьмем, напри- мер, программу static2.c.

    84
    0x200 Программирование
    static2.c
    #include
    void function() { // Пример функции с собственным контекстом int var = 5;
    static int static_var = 5; // Инициализация статической переменной printf(“\t[in function] var @ %p = %d\n”, &var, var);
    printf(“\t[in function] static_var @ %p = %d\n”, &static_var, static_var);
    var++; // Прибавить 1 к var.
    static_var++; // Прибавить 1 к static_var.
    }
    int main() { // Функция main с собственным контекстом int i;
    static int static_var = 1337; // Другая статическая, в другом контексте for(i=0; i < 5; i++) { // loop 5 times printf(“[in main] static_var @ %p = %d\n”, &static_var, static_var);
    function(); // Вызвать функцию.
    }
    }
    Результат компиляции и выполнения static2.c:
    reader@hacking:/booksrc $ gcc static2.c reader@hacking:/booksrc $ ./a.out
    [in main] static_var @ 0x804968c = 1337
    [in function] var @ 0xbffff814 = 5
    [in function] static_var @ 0x8049688 = 5
    [in main] static_var @ 0x804968c = 1337
    [in function] var @ 0xbffff814 = 5
    [in function] static_var @ 0x8049688 = 6
    [in main] static_var @ 0x804968c = 1337
    [in function] var @ 0xbffff814 = 5
    [in function] static_var @ 0x8049688 = 7
    [in main] static_var @ 0x804968c = 1337
    [in function] var @ 0xbffff814 = 5
    [in function] static_var @ 0x8049688 = 8
    [in main] static_var @ 0x804968c = 1337
    [in function] var @ 0xbffff814 = 5
    [in function] static_var @ 0x8049688 = 9
    reader@hacking:/booksrc $
    По выведенным адресам переменных видно, что static_var в main() от- личается от переменной с тем же именем в function(), потому что у них разные адреса памяти (0x804968c и 0x8049688 соответственно). Вы, на- верное, заметили, что адреса локальных переменных очень большие, например 0xbffff814, а глобальных и статических – очень маленькие, например 0x0804968c и 0x8049688. Хорошо, что вы так наблюдательны:

    0x270 Сегментация памяти
    85
    обнаружение таких мелких фактов и выяснение причин их появления составляет один из краеугольных камней хакинга. Читайте дальше, и вы поймете почему.
    0x270 Сегментация памяти
    Память, занимаемая скомпилированной программой, делится на пять сегментов: текст, или код (text), данные (data), bss, куча (heap) и стек
    (stack). Каждый сегмент представляет собой особый раздел памяти, выделенный для специальных целей.
    Сегмент text иногда также называют сегментом кода. В нем распола- гаются машинные команды программы. Выполнение команд в этом сегменте происходит нелинейно из-за упоминавшихся выше управля- ющих структур верхнего уровня и функций, которые компилируют- ся в инструкции ветвления, перехода и вызова функций на языке ас- семблера. При запуске программы EIP устанавливается на первую ин- струкцию в сегменте text. Затем процессор осуществляет цикл испол- нения, в котором происходит следующее:
    1. Считывается команда по адресу, находящемуся в EIP.
    2. К EIP прибавляется длина этой команды в байтах.
    3. Выполняется команда, прочитанная на шаге 1.
    4. Происходит переход к шагу 1.
    Если команда выполняет переход или вызов, она заменяет EIP другим адресом. Процессору это безразлично, потому что он не ориентирован на линейное выполнение команд. Если на шаге 3 EIP изменится, про- цессор перейдет к шагу 1 и прочтет ту инструкцию, которая находится по адресу, записанному в EIP.
    В сегменте text запись запрещена: в нем хранится только код, но не пе- ременные. Это защищает код программы от модификации: при попыт- ке записи в этот сегмент памяти программа сообщает пользователю, что происходит нечто неладное, и завершает свою работу. Другое пре- имущество доступности этого сегмента лишь для чтения состоит в том, что несколько запущенных экземпляров одной программы могут ис- пользовать его совместно, не мешая один другому. Следует также отме- тить, что размер этого сегмента памяти постоянен, потому что в нем не происходит никаких изменений.
    Сегмент данных и сегмент bss предназначены для хранения глобаль- ных и статических переменных программы. В сегменте data хранятся инициализированные глобальные и статические переменные, а в bss – такие же переменные без инициализации. Эти сегменты доступны для записи, но их размер также фиксирован. Вспомним, что глобальные переменные сохраняются независимо от функционального контекста
    (как переменная j в предыдущих примерах). Глобальные и статические

    86
    0x200 Программирование переменные способны сохранять свои значения, поскольку хранятся в отдельных сегментах памяти.
    Сегмент кучи (heap) непосредственно доступен программисту. Он мо- жет выделять в этом сегменте блоки памяти и использовать их по свое- му усмотрению. Примечательная особенность кучи – ее непостоянный размер, способный увеличиваться или уменьшаться по мере необходи- мости. Память в куче управляется алгоритмами выделения (allocator) и освобождения (deallocator): первый резервирует участки памяти для использования, а второй освобождает их, делая возможным повторное резервирование. Размер кучи увеличивается или уменьшается в зави- симости от того, сколько памяти зарезервировано для использования.
    Программист, таким образом, может с помощью функций выделения памяти динамически резервировать и освобождать память. Увеличе- ние размеров кучи сопровождается ее ростом вниз, в направлении стар- ших адресов.
    Сегмент стека (stack) – тоже переменного размера; он служит вре- менным хранилищем локальных переменных и контекстов функций при их вызове. Именно его показывает команда обратной трассиров- ки в GDB. Когда программа вызывает некоторую функцию, та получа- ет собственный набор переданных ей переменных, а код функции рас- полагается по отдельному адресу в сегменте текста (кода). Поскольку при вызове функции изменяются контекст и EIP, в стек помещаются передаваемые переменные, адрес, к которому EIP должен вернуться по завершении работы функции, и локальные переменные этой функции.
    Все эти данные хранятся вместе на стеке в так называемом кадре сте-
    ка. Стек содержит много кадров.
    В информатике стеком называется часто используемая абстрактная структура данных. Для нее действует правило «первым пришел – по- следним ушел» (FILO), согласно которому первый объект, помещенный на стек, будет последним, взятым с него. Наглядная аналогия – нани- зывание бусин на нитку с узлом на конце: нельзя снять первую бусин- ку, не сняв перед тем все остальные. Помещение элемента в стек назы- вают также проталкиванием (pushing), а его извлечение – выталкива-
    нием (popping).
    В соответствии со своим названием сегмент стека в памяти является стековой структурой, хранящей кадры (фреймы) стека. Адрес верши- ны стека хранится в ESP, он постоянно изменяется по мере помещения в стек новых элементов и их извлечения. Ввиду столь высокой динами- ки стека вполне логично, что его размер не фиксирован. Однако в отли- чие от кучи, при увеличении размера стека его вершина движется в па- мяти «вверх», в направлении младших адресов.
    Порядок FILO, действующий в стеке, может показаться странным, од- нако он очень удобен для хранения контекста. При вызове функции в кадр стека помещается группа данных. Для обращения к локальным переменным функции, располагающимся в текущем кадре стека, слу-

    0x270 Сегментация памяти
    87
    жит регистр EBP, который называют указателем кадра (frame pointer,
    FP) или указателем локальной базы (local base, LB). В каждом кадре стека содержатся переданные функции параметры, ее локальные пере- менные и два указателя, необходимых для того, чтобы вернуть все на место: сохраненный указатель кадра (saved frame pointer, SFP) и адрес
    возврата. С помощью SFP регистр EBP возвращается в исходное состо- яние, а с помощью адреса возврата в EIP записывается адрес коман- ды, следующей за вызовом функции. В результате восстанавливается функциональный контекст предшествующего кадра стека.
    Приведенный ниже код stack_example.c содержит две функции: main() и test_function().
    stack_example.c
    void test_function(int a, int b, int c, int d) {
    int flag;
    char buffer[10];
    flag = 31337;
    buffer[0] = ‘A’;
    }
    int main() {
    test_function(1, 2, 3, 4);
    }
    Сначала эта программа определяет функцию test_function с четырь- мя целочисленными аргументами: a, b, c и d. Локальные переменные функции – это 4-байтная переменная flag и 10-символьный буфер buf- fer
    . Память этим переменным выделяется в сегменте стека, а машин- ные команды кода функции хранятся в сегменте text. Скомпилиро- вав программу, можно изучить ее внутреннее устройство с помощью
    GDB. Ниже показан результат дизассемблирования машинных ко- манд для функций main() и test_function(). Функция main() начинается с 0x08048357, а функция test_function() – с 0x08048344. Первые несколько команд в каждой функции (в следующем листинге выделены полужир- ным) организуют кадр стека. В совокупности эти команды называют- ся прологом процедуры или прологом функции. Они записывают в стек указатель кадра и локальные переменные. Иногда в прологе функции выполняется также некоторое выравнивание стека. Точный вид проло- га может весьма различаться в зависимости от компилятора и его оп- ций, но общая задача его команд – выстроить кадр стека.
    reader@hacking:/booksrc $ gcc -g stack_example.c reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) disass main
    Dump of assembler code for function main():
    0x08048357 : push ebp
    0x08048358 : mov ebp,esp
    0x0804835a : sub esp,0x18
    0x0804835d : and esp,0xfffffff0

    88
    0x200 Программирование
    0x08048360 : mov eax,0x0
    0x08048365 : sub esp,eax
    0x08048367 : mov DWORD PTR [esp+12],0x4 0x0804836f : mov DWORD PTR [esp+8],0x3 0x08048377 : mov DWORD PTR [esp+4],0x2 0x0804837f : mov DWORD PTR [esp],0x1 0x08048386 : call 0x8048344
    0x0804838b : leave
    0x0804838c : ret
    End of assembler dump
    (gdb) disass test_function()
    Dump of assembler code for function test_function:
    0x08048344 : push ebp
    0x08048345 : mov ebp,esp
    0x08048347 : sub esp,0x28
    0x0804834a : mov DWORD PTR [ebp-12],0x7a69 0x08048351 : mov BYTE PTR [ebp-40],0x41 0x08048355 : leave
    0x08048356 : ret
    End of assembler dump
    (gdb)
    При выполнении этой программы вызывается функция main(), которая просто вызывает функцию test_function().
    При вызове функции test_function() из функции main() в стек помеща- ются различные значения, образующие начало кадра стека. При этом аргументы функции проталкиваются в стек в обратном порядке (по- скольку в нем применяется принцип FILO). Аргументами функции служат 1, 2, 3 и 4, поэтому последовательность команд помещает в стек
    4, 3, 2 и наконец 1. Эти значения соответствуют переменным d, c, b и a функции. Команды, помещающие эти значения в стек, в приведенном ниже результате дизассемблирования функции main() выделены полу- жирным.
    (gdb) disass main
    Dump of assembler code for function main:
    0x08048357 : push ebp
    0x08048358 : mov ebp,esp
    0x0804835a : sub esp,0x18 0x0804835d : and esp,0xfffffff0 0x08048360 : mov eax,0x0 0x08048365 : sub esp,eax
    0x08048367 : mov DWORD PTR [esp+12],0x4
    0x0804836f : mov DWORD PTR [esp+8],0x3
    0x08048377 : mov DWORD PTR [esp+4],0x2
    0x0804837f : mov DWORD PTR [esp],0x1
    0x08048386 : call 0x8048344
    0x0804838b : leave
    0x0804838c : ret
    End of assembler dump
    (gdb)

    0x270 Сегментация памяти
    89
    Затем при выполнении команды call ассемблера в стек помещается адрес возврата, а выполнение передается на начало функции test_func- tion()
    с адресом 0x08048344. Адрес возврата – это местонахождение ко- манды, следующей за текущей, адрес которой содержится в EIP, а имен- но значение, сохраненное на шаге 3 обсуждавшегося цикла исполне- ния. В данном случае должен произойти возврат на адрес 0x0804838b, где в функции main() размещается команда leave.
    Команда call сохраняет в стеке адрес возврата и выполняет переход по содержащемуся в EIP адресу начала функции test_function(); таким об- разом, команды пролога функции test_function() завершили создание кадра стека. Теперь в стек помещается текущее значение EBP. Оно на- зывается сохраненным указателем кадра (SFP) и позволяет позже вер- нуть EBP в исходное состояние. Затем текущее значение ESP копирует- ся в EBP, чтобы установить новый указатель кадра. Этот указатель ка- дра используется для обращения к локальным переменным функции
    (flag и buffer). Память для этих переменных отводится в результате вы- читания из ESP. В конечном счете кадр стека выглядит примерно так, как показано на рис. 2.1.
    buffer flag указатель кадра стека (SFP)
    адрес возврата (ret)
    a b
    c d
    Вершина стека
    Указатель кадра (EBP)
    Младшие адреса
    Старшие адреса
    Рис. 2.1. Кадр стека
    С помощью GDB можно проследить, как в стеке формируется кадр.
    В следующем листинге точки останова установлены в main() перед об- ращением к test_function() и в начале test_function(). GDB помещает первую точку останова перед командами, отправляющими в стек ар- гументы функции, а вторую точку останова – после пролога функции test_function()
    После запуска программы исполнение останавливается в точке остано- ва, где исследуется содержимое регистров ESP (указатель стека), EBP
    (указатель кадра) и EIP (указатель команды).

    90
    0x200 Программирование
    (gdb) list main
    4 5 flag = 31337;
    6 buffer[0] = ‘A’;
    7 }
    8 9 int main() {
    10 test_function(1, 2, 3, 4);
    11 }
    (gdb) break 10
    Breakpoint 1 at 0x8048367: file stack_example.c, line 10.
    (gdb) break test_function
    Breakpoint 2 at 0x804834a: file stack_example.c, line 5.
    (gdb) run
    Starting program: /home/reader/booksrc/a.out
    Breakpoint 1, main () at stack_example.c:10 10 test_function(1, 2, 3, 4);
    (gdb) i r esp ebp eip esp 0xbffff7f0 0xbffff7f0
    ebp 0xbffff808 0xbffff808
    eip 0x8048367 0x8048367
    (gdb) x/5i $eip
    0x8048367 : mov DWORD PTR [esp+12],0x4 0x804836f : mov DWORD PTR [esp+8],0x3 0x8048377 : mov DWORD PTR [esp+4],0x2 0x804837f : mov DWORD PTR [esp],0x1 0x8048386 : call 0x8048344
    (gdb)
    Эта точка останова находится как раз перед тем местом, где форми- руется кадр стека для вызова test_function(). То есть дно этого ново- го кадра стека находится по адресу, являющемуся текущим значе- нием ESP, 0xbffff7f0. Следующая точка останова расположена сразу после пролога функции test_function(), поэтому при продолжении ра- боты будет построен кадр стека. В следующем листинге показана ана- логичная информация для второй точки останова. Обращение к ло- кальным переменным (flag и buffer) происходит относительно указа- теля кадра (EBP).
    (gdb) cont
    Continuing.
    Breakpoint 2, test_function (a=1, b=2, c=3, d=4) at stack_example.c:5 5 flag = 31337;
    (gdb) i r esp ebp eip esp 0xbffff7c0 0xbffff7c0
    ebp 0xbffff7e8 0xbffff7e8
    eip 0x804834a 0x804834a
    (gdb) disass test_function
    Dump of assembler code for function test_function:

    0x270 Сегментация памяти
    91
    0x08048344 : push ebp
    0x08048345 : mov ebp,esp
    0x08048347 : sub esp,0x28 0x0804834a : mov DWORD PTR [ebp-12],0x7a69 0x08048351 : mov BYTE PTR [ebp-40],0x41 0x08048355 : leave
    0x08048356 : ret
    End of assembler dump.
    (gdb) print $ebp-12
    $1 = (void *) 0xbffff7dc
    (gdb) print $ebp-40
    $2 = (void *) 0xbffff7c0
    (gdb) x/16xw $esp
    0xbffff7c0:
    1 0x00000000 0x08049548 0xbffff7d8 0x08048249 0xbffff7d0: 0xb7f9f729 0xb7fd6ff4 0xbffff808 2
    0x080483b9 0xbffff7e0: 0xb7fd6ff4 0xbffff89c
    3 0xbffff808 4
    0x0804838b
    0xbffff7f0:
    5
    1   ...   6   7   8   9   10   11   12   13   ...   51


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