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

  • 0x8048393 : mov DWORD PTR [esp],0x8048484

  • 154 108 6C l 055 45 2D - 155 109 6D m 056 46 2E . 156 110 6E n 057 47 2F / 157 111 6F o

  • 0x0804839f : lea eax,[ebp-4] 0x080483a2 : inc DWORD PTR [eax] 0x080483a4 : jmp 0x804838b

  • 6 for(i=0; i 7 { 8 printf(“Hello, world!\n”); 9 }

  • 0x260 Возвращаемся к основам

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


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

    45
    Эта команда ассемблера поместит значение 0 в ячейку памяти, адрес которой на 4 меньше того, что содержится в регистре EBP. Это адрес, по которому хранится переменная i кода C; i была объявлена как це- лое число, использующее 4 байта памяти в архитектуре x86. По суще- ству, данная команда обнулит переменную i для цикла for. Если по- смотреть сейчас содержимое памяти по этому адресу, там обнаружит- ся случайный мусор. Отобразить содержимое памяти можно разными способами.
    (gdb) i r ebp ebp 0xbffff808 0xbffff808
    (gdb) x/4xb $ebp - 4 0xbffff804: 0xc0 0x83 0x04 0x08
    (gdb) x/4xb 0xbffff804 0xbffff804: 0xc0 0x83 0x04 0x08
    (gdb) print $ebp - 4
    $1 = (void *) 0xbffff804
    (gdb) x/4xb $1 0xbffff804: 0xc0 0x83 0x04 0x08
    (gdb) x/xw $1 0xbffff804: 0x080483c0
    (gdb)
    Мы видим, что регистр EBP содержит адрес 0xbffff808, а команда ас- семблера будет выполнять запись в ячейку с адресом, смещенным от- носительно него на 4 байта, то есть 0xbffff804. Можно прямо дать ко- манде examine этот адрес памяти, а можно заставить ее саму заниматься арифметикой. Простые математические действия можно выполнить с помощью команды print, результат которой также записывается от- ладчиком во временную переменную. Имя этой переменной – $1, поз- же она позволит снова быстро обратиться к определенному адресу па- мяти. Все приведенные выше способы делают одно и то же: отобража- ют 4 байта мусора в том месте памяти, которое будет очищено в резуль- тате выполнения текущей команды.
    Давайте выполним текущую команду с помощью команды отладчика nexti
    (от next instruction). Процессор прочтет команду по адресу, нахо- дящемуся в EIP, выполнит ее и переведет EIP на следующую команду.
    (gdb) nexti
    0x0804838b 6 for(i=0; i < 10; i++)
    (gdb) x/4xb $1 0xbffff804: 0x00 0x00 0x00 0x00
    (gdb) x/dw $1 0xbffff804: 0
    (gdb) i r eip eip 0x804838b 0x804838b
    (gdb) x/i $eip
    0x804838b : cmp DWORD PTR [ebp-4],0x9
    (gdb)

    46
    0x200 Программирование
    Как мы и ожидали, предыдущая команда обнулила 4 байта по адресу
    EBP минус 4, представляющему собой память, отведенную для пере- менной i. После этого EIP перешел к следующей команде. Несколько следующих команд лучше обсуждать как единую группу.
    (gdb) x/10i $eip
    0x804838b : cmp DWORD PTR [ebp-4],0x9 0x804838f : jle 0x8048393
    0x8048391 : jmp 0x80483a6
    0x8048393 : mov DWORD PTR [esp],0x8048484
    0x804839a : call 0x80482a0 0x804839f : lea eax,[ebp-4]
    0x80483a2 : inc DWORD PTR [eax]
    0x80483a4 : jmp 0x804838b
    0x80483a6 : leave
    0x80483a7 : ret
    (gdb)
    Первая команда cmp (от compare) выполняет сравнение, в данном случае содержимого памяти, отведенной для переменной i языка C, со значе- нием 9. Следующая команда jle выполняет переход при условии «мень- ше или равно». При этом проверяется результат предыдущей коман- ды сравнения (хранящийся в регистре EFLAGS), и если он совпадает с «меньше или равно», то EIP перенацеливается на совсем другой уча- сток кода. В данном случае инструкция говорит, что нужно перейти по адресу 0x8048393, если хранящееся в памяти значение переменной i мень- ше или равно 9. Если это не так, EIP укажет на следующую за ней ко- манду, выполняющую безусловный переход. Она переведет EIP на адрес
    0x80483a6
    . Эти три команды вместе образуют управляющую структуру if-then-else
    : если значение i меньше или равно 9, то перейти к коман-
    де по адресу 0x8048393; в противном случае перейти к команде по адре-
    су 0x80483a6. Первый адрес 0x8048393 (выделен полужирным) – это просто команда, идущая после команды безусловного перехода, а второй адрес
    0x80483a6
    (выделен курсивом) находится в конце функции.
    Мы знаем, что по адресу памяти, который сравнивается с 9, находится
    0, и знаем, что 0 меньше или равен 9, поэтому после выполнения следу- ющих двух команд в EIP будет находиться 0x8048393.
    (gdb) nexti
    0x0804838f 6 for(i=0; i < 10; i++)
    (gdb) x/i $eip
    0x804838f : jle 0x8048393
    (gdb) nexti
    8 printf(“Hello, world!\n”);
    (gdb) i r eip eip 0x8048393 0x8048393
    (gdb) x/2i $eip
    0x8048393 : mov DWORD PTR [esp],0x8048484 0x804839a : call 0x80482a0
    (gdb)

    0x250 Практическая работа
    47
    Как и ожидалось, две предыдущие команды продолжили выполнение программы с адреса 0x8048393, где мы встречаем следующие две коман- ды. Первая – mov, которая запишет значение 0x8048484 в ячейку памя- ти, адрес которой содержится в регистре ESP. Но куда указывает ре- гистр ESP?
    (gdb) i r esp esp 0xbffff800 0xbffff800
    (gdb)
    В данный момент ESP содержит адрес 0xbffff800, а после выполнения команды mov по этому адресу будет записан адрес 0x8048484. Но зачем?
    Что такого интересного находится по адресу 0x8048484? Это можно вы- яснить.
    (gdb) x/2xw 0x8048484 0x8048484: 0x6c6c6548 0x6f57206f
    (gdb) x/6xb 0x8048484 0x8048484: 0x48 0x65 0x6c 0x6c 0x6f 0x20
    (gdb) x/6ub 0x8048484 0x8048484: 72 101 108 108 111 32
    (gdb)
    Опытный читатель может кое-что заметить в этих данных, в частности диапазон значений байтов. Поизучав некоторое время память, вы ста- нете быстро определять такого рода систему. Дело в том, что эти байты соответствуют диапазону отображаемых символов ASCII. ASCII – это стандарт отображения всех символов, которые вы видите на своей кла- виатуре (и некоторых других), в фиксированные числа. Байты 0x48,
    0x65, 0x6c и 0x6f соответствуют буквам алфавита в таблице ASCII, ко- торая приведена ниже. Таблицу ASCII можно вывести командой man ascii
    , которая есть в большинстве систем UNIX.
    ASCII Table
    Oct Dec Hex Char Oct Dec Hex Char
    -----------------------------------------------------------
    000 0 00 NUL ‘\0’ 100 64 40 @
    001 1 01 SOH 101 65 41 A
    002 2 02 STX 102 66 42 B
    003 3 03 ETX 103 67 43 C
    004 4 04 EOT 104 68 44 D
    005 5 05 ENQ 105 69 45 E
    006 6 06 ACK 106 70 46 F
    007 7 07 BEL ‘\a’ 107 71 47 G
    010 8 08 BS ‘\b’ 110 72 48 H
    011 9 09 HT ‘\t’ 111 73 49 I
    012 10 0A LF ‘\n’ 112 74 4A J
    013 11 0B VT ‘\v’ 113 75 4B K
    014 12 0C FF ‘\f’ 114 76 4C L
    015 13 0D CR ‘\r’ 115 77 4D M

    48
    0x200 Программирование
    016 14 0E SO 116 78 4E N
    017 15 0F SI 117 79 4F O
    020 16 10 DLE 120 80 50 P
    021 17 11 DC1 121 81 51 Q
    022 18 12 DC2 122 82 52 R
    023 19 13 DC3 123 83 53 S
    024 20 14 DC4 124 84 54 T
    025 21 15 NAK 125 85 55 U
    026 22 16 SYN 126 86 56 V
    027 23 17 ETB 127 87 57 W
    030 24 18 CAN 130 88 58 X
    031 25 19 EM 131 89 59 Y
    032 26 1A SUB 132 90 5A Z
    033 27 1B ESC 133 91 5B [
    034 28 1C FS 134 92 5C \ ‘\\’
    035 29 1D GS 135 93 5D ]
    036 30 1E RS 136 94 5E ^
    037 31 1F US 137 95 5F _
    040 32 20 SPACE 140 96 60 `
    041 33 21 ! 141 97 61 a
    042 34 22 “ 142 98 62 b
    043 35 23 # 143 99 63 c
    044 36 24 $ 144 100 64 d
    045 37 25 % 145 101 65 e
    046 38 26 & 146 102 66 f
    047 39 27 ‘ 147 103 67 g
    050 40 28 ( 150 104 68 h
    051 41 29 ) 151 105 69 i
    052 42 2A * 152 106 6A j
    053 43 2B + 153 107 6B k
    054 44 2C , 154 108 6C l
    055 45 2D - 155 109 6D m
    056 46 2E . 156 110 6E n
    057 47 2F / 157 111 6F o
    060 48 30 0 160 112 70 p
    061 49 31 1 161 113 71 q
    062 50 32 2 162 114 72 r
    063 51 33 3 163 115 73 s
    064 52 34 4 164 116 74 t
    065 53 35 5 165 117 75 u
    066 54 36 6 166 118 76 v
    067 55 37 7 167 119 77 w
    070 56 38 8 170 120 78 x
    071 57 39 9 171 121 79 y
    072 58 3A : 172 122 7A z
    073 59 3B ; 173 123 7B {
    074 60 3C < 174 124 7C |
    075 61 3D = 175 125 7D }
    076 62 3E > 176 126 7E


    077 63 3F ? 177 127 7F DEL

    0x250 Практическая работа
    49
    К счастью, команда GDB examine позволяет отображать и такое содер- жимое памяти. Формат c автоматически покажет байты согласно та- блице ASCII, а формат s покажет всю строку символьных данных.
    (gdb) x/6cb 0x8048484 0x8048484: 72 ‘H’ 101 ‘e’ 108 ‘l’ 108 ‘l’ 111 ‘o’ 32 ‘ ‘
    (gdb) x/s 0x8048484 0x8048484: “Hello, world!\n”
    (gdb)
    Эти команды показывают, что по адресу 0x8048484 хранится строка
    «Hello, world!\n»
    . Эта строка передается в качестве аргумента функ- ции printf(), указывающего, что запись адреса этой строки (0x8048484) в ячейку, адрес которой содержит ESP, как-то связана с этой функци- ей. Следующий листинг показывает запись адреса этой строки данных по адресу, на который указывает ESP.
    (gdb) x/2i $eip
    0x8048393 : mov DWORD PTR [esp],0x8048484 0x804839a : call 0x80482a0
    (gdb) x/xw $esp
    0xbffff800: 0xb8000ce0
    (gdb) nexti
    0x0804839a 8 printf(“Hello, world!\n”);
    (gdb) x/xw $esp
    0xbffff800: 0x08048484
    (gdb)
    Следующая команда является вызовом функции printf(), которая вы- водит строку данных. Предыдущая команда служила подготовкой к вызову функции, а результат вызова функции выделен ниже полу- жирным.
    (gdb) x/i $eip
    0x804839a : call 0x80482a0
    (gdb) nexti
    Hello, world!
    6 for(i=0; i < 10; i++)
    (gdb)
    Продолжая отладку с помощью GDB, посмотрим на следующие две ко- манды. Их тоже лучше рассмотреть совместно.
    (gdb) x/2i $eip
    0x804839f : lea eax,[ebp-4]
    0x80483a2 : inc DWORD PTR [eax]
    (gdb)
    По сути, эти две инструкции просто увеличивают переменную i на 1.
    Команда lea (от Load Effective Address – загрузка эффективного адре- са) загружает уже знакомый адрес EBP минус 4 в регистр EAX. Ниже показан результат ее выполнения.

    50
    0x200 Программирование
    (gdb) x/i $eip
    0x804839f : lea eax,[ebp-4]
    (gdb) print $ebp - 4
    $2 = (void *) 0xbffff804
    (gdb) x/x $2 0xbffff804: 0x00000000
    (gdb) i r eax eax 0xd 13
    (gdb) nexti
    0x080483a2 6 for(i=0; i < 10; i++)
    (gdb) i r eax eax 0xbffff804 -1073743868
    (gdb) x/xw $eax
    0xbffff804: 0x00000000
    (gdb) x/dw $eax
    0xbffff804: 0
    (gdb)
    Следующая команда inc увеличит на 1 значение, находящееся по это- му адресу (теперь записанному в регистр EAX). Выполнение этой ко- манды показано ниже.
    (gdb) x/i $eip
    0x80483a2 : inc DWORD PTR [eax]
    (gdb) x/dw $eax
    0xbffff804: 0
    (gdb) nexti
    0x080483a4 6 for(i=0; i < 10; i++)
    (gdb) x/dw $eax
    0xbffff804: 1
    (gdb)
    В результате значение, хранящееся по адресу EBP минус 4 (0xbffff804), увеличивается на 1. Это соответствует той части кода на C, где в цикле for увеличивается значение переменной i.
    Следующая команда – безусловный переход.
    (gdb) x/i $eip
    0x80483a4 : jmp 0x804838b
    (gdb)
    В результате выполнения этой команды программа снова возвращает- ся к инструкции по адресу 0x804838b. Для этого данный адрес просто за- писывается в EIP.
    Теперь, глядя на весь дизассемблированный код, вы можете сказать, ка- кие части кода C в какие машинные команды были скомпилированы.
    (gdb) disass main
    Dump of assembler code for function main:
    0x08048374 : push ebp
    0x08048375 : mov ebp,esp
    0x08048377 : sub esp,0x8

    0x260 Возвращаемся к основам
    51
    0x0804837a : and esp,0xfffffff0 0x0804837d : mov eax,0x0 0x08048382 : sub esp,eax
    0x08048384 : mov DWORD PTR [ebp-4],0x0
    0x0804838b : cmp DWORD PTR [ebp-4],0x9
    0x0804838f : jle 0x8048393
    0x08048391 : jmp 0x80483a6
    0x08048393 : mov DWORD PTR [esp],0x8048484
    0x0804839a : call 0x80482a0

    0x0804839f : lea eax,[ebp-4]
    0x080483a2 : inc DWORD PTR [eax]
    0x080483a4 : jmp 0x804838b
    0x080483a6 : leave
    0x080483a7 : ret
    End of assembler dump.
    (gdb) list
    1 #include
    2 3 int main()
    4 {
    5 int i;
    6 for(i=0; i < 10; i++)
    7 {
    8 printf(“Hello, world!\n”);
    9 }
    10 }
    (gdb)
    Команды, выделенные полужирным, образуют цикл for, а те, что кур- сивом – находящийся внутри цикла вызов printf(). Выполнение про- граммы переходит обратно к команде сравнения, затем происходит вы- зов printf() и переменная счетчика увеличивается, пока ее значение не достигнет 10. В этот момент условный переход jle не произойдет, вме- сто этого указатель команды перейдет на команду безусловного пере- хода, которая выполнит выход из цикла и завершит программу.
    0x260 Возвращаемся к основам
    Теперь, когда ваше представление о программировании стало более конкретным, следует познакомиться с некоторыми другими важными аспектами языка C. Ассемблер и процессоры появились раньше язы- ков программирования высокого уровня, и за это время в программи- ровании возникло много новых идей. Подобно тому как некоторое зна- комство с латынью может заметно помочь более глубокому пониманию английского языка, представление о низкоуровневом программирова- нии может облегчить понимание программирования на языках более высокого уровня. Переходя к следующему разделу, не забывайте, что код C может что-либо сделать лишь после того, как будет скомпилиро- ван в машинные команды.

    52
    0x200 Программирование
    0x261 Строки
    Значение «Hello, world!\n», передаваемое функции printf() в предыду- щей программе, является строкой – формально, массивом символов.
    Массив в C это просто список из n элементов определенного типа. Мас- сив из 20 символов это 20 символов, размещенных в памяти один за другим. Массивы также называют буферами. Программа char_array.c содержит пример массива символов.
    char_array.c
    #include
    int main()
    {
    char str_a[20];
    str_a[0] = ‘H’;
    str_a[1] = ‘e’;
    str_a[2] = ‘l’;
    str_a[3] = ‘l’;
    str_a[4] = ‘o’;
    str_a[5] = ‘,’;
    str_a[6] = ‘ ‘;
    str_a[7] = ‘w’;
    str_a[8] = ‘o’;
    str_a[9] = ‘r’;
    str_a[10] = ‘l’;
    str_a[11] = ‘d’;
    str_a[12] = ‘!’;
    str_a[13] = ‘\n’;
    str_a[14] = 0;
    printf(str_a);
    }
    Компилятор GCC принимает ключ -o, с помощью которого можно за- дать имя выходного файла, содержащего результат компиляции.
    Ниже показано, как скомпилировать эту программу в исполняемый двоичный файл char_array.
    reader@hacking:/booksrc $ gcc -o char_array char_array.c reader@hacking:/booksrc $ ./char_array
    Hello, world!
    reader@hacking:/booksrc $
    В данной программе определен массив str_a из 20 символов, и во все элементы этого массива поочередно выполняется запись. Обратите внимание: нумерация элементов начинается с 0, а не с 1. Кроме того, последним символом является 0 (null, или нулевой байт).
    Поскольку массив определен как символьный, ему было выделено
    20 байт памяти, но фактически использовано из них только 12. Нуле- вой байт в конце играет роль разделителя, сообщая функциям, рабо-

    0x260 Возвращаемся к основам
    53
    тающим с этой строкой, что в этом месте они должны остановиться.
    Оставшиеся байты будут проигнорированы. Если ввести нулевой байт в качестве пятого элемента, то функция printf() выведет только сим- волы Hello.
    Задавать каждый символ в массиве утомительно, а строки использу- ются достаточно часто, поэтому для обработки строк был создан набор стандартных функций. Например, функция strcpy() копирует стро- ку из источника в приемник, последовательно просматривая строку- источник и копируя каждый байт в строку-приемник (останавливаясь при обнаружении нулевого байта – конца строки). Порядок аргумен- тов функции такой же, как в асемблере Intel: сначала приемник, за- тем источник.
    Программу char_array.c можно переписать с использованием функции strcpy()
    , так что она будет выполнять ту же задачу с применением би- блиотеки строковых функций. В следующей версии программы char_
    array включается заголовочный файл string.h, поскольку в ней исполь- зуется строковая функция.
    char_array2.c
    #include
    #include
    int main() {
    char str_a[20];
    strcpy(str_a, “Hello, world!\n”);
    printf(str_a);
    }
    Изучим эту программу с помощью GDB. В приведенном ниже листин- ге скомпилированная программа открывается в отладчике, и устанав- ливаются точки останова до функции strcpy(), на ней и после нее, что отмечено полужирным. Отладчик остановит выполнение программы в каждой из этих точек, что позволит просмотреть содержимое реги- стров и памяти. Код функции strcpy() берется из библиотеки общего доступа, поэтому задать точку останова внутри этой функции можно только после начала выполнения программы.
    reader@hacking:/booksrc $ gcc -g -o char_array2 char_array2.c reader@hacking:/booksrc $ gdb -q ./char_array2
    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];
    6
    7 strcpy(str_a, «Hello, world!\n»);

    54
    0x200 Программирование
    8 printf(str_a);
    9 }
    (gdb) break 6
    Breakpoint 1 at 0x80483c4: file char_array2.c, line 6.
    (gdb) break strcpy
    Function “strcpy” not defined.
    Make breakpoint pending on future shared library load? (y or [n]) y
    Breakpoint 2 (strcpy) pending.
    (gdb) break 8
    Breakpoint 3 at 0x80483d7: file char_array2.c, line 8.
    (gdb)
    При запуске программы обрабатывается точка останова strcpy().
    В каждой точке останова мы просмотрим содержимое EIP и команды, на которые он указывает. Обратите внимание: в средней точке адрес памяти, находящийся в EIP, не такой, как в других.
    (gdb) run
    Starting program: /home/reader/booksrc/char_array2
    Breakpoint 4 at 0xb7f076f4
    Pending breakpoint “strcpy” resolved
    Breakpoint 1, main () at char_array2.c:7 7 strcpy(str_a, “Hello, world!\n”);
    (gdb) i r eip eip 0x80483c4 0x80483c4
    (gdb) x/5i $eip
    0x80483c4 : mov DWORD PTR [esp+4],0x80484c4 0x80483cc : lea eax,[ebp-40]
    0x80483cf : mov DWORD PTR [esp],eax
    0x80483d2 : call 0x80482c4
    0x80483d7 : lea eax,[ebp-40]
    (gdb) continue
    Continuing.
    Breakpoint 4, 0xb7f076f4 in strcpy () from /lib/tls/i686/cmov/libc.so.6
    (gdb) i r eip
    1   2   3   4   5   6   7   8   9   ...   51


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