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

  • 0x253 Язык ассемблера

  • 0x08048394 : mov DWORD PTR [ebp-4],0x0

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


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

    36
    0x200 Программирование и вкусами. Теоретически можно разработать собственный синтаксис ассемблера для x86, но большинство программистов придерживаются двух главных типов – синтаксиса AT&T и синтаксиса Intel. Ассемблер в приведенном листинге соответствует синтаксису AT&T, поскольку практически все средства дизассемблирования в Linux по умолчанию используют этот синтаксис. Синтаксис AT&T легко узнать по много- численным символам % и $, с которых начинается почти всё (взгляни- те еще раз на приведенный пример). Тот же код можно вывести в син- таксисе Intel, указав для objdump дополнительный параметр командной строки -M intel и получив следующий результат.
    reader@hacking:

    /booksrc $ objdump -M intel -D a.out | grep -A20 main.:
    08048374
    :
    8048374: 55 push ebp
    8048375: 89 e5 mov ebp,esp
    8048377: 83 ec 08 sub esp,0x8 804837a: 83 e4 f0 and esp,0xfffffff0 804837d: b8 00 00 00 00 mov eax,0x0 8048382: 29 c4 sub esp,eax
    8048384: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-4],0x0 804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4],0x9 804838f: 7e 02 jle 8048393
    8048391: eb 13 jmp 80483a6
    8048393: c7 04 24 84 84 04 08 mov DWORD PTR [esp],0x8048484 804839a: e8 01 ff ff ff call 80482a0 804839f: 8d 45 fc lea eax,[ebp-4]
    80483a2: ff 00 inc DWORD PTR [eax]
    80483a4: eb e5 jmp 804838b
    80483a6: c9 leave
    80483a7: c3 ret
    80483a8: 90 nop
    80483a9: 90 nop
    80483aa: 90 nop reader@hacking:/booksrc $
    Лично я считаю синтаксис Intel более легким и понятным, поэтому и буду придерживаться его в этой книге. В обоих синтаксисах коман- ды, понимаемые процессором, выглядят весьма просто. Они состоят из операции и в некоторых случаях дополнительных аргументов, описы- вающих конечный и/или исходный адрес операции. Эти команды пе- ремещают содержимое памяти, выполняют элементарные математи- ческие действия либо прерывают работу процессора, чтобы он занял- ся чем-то другим. В конце концов, это все, что реально умеет делать процессор компьютера. Но точно так же, как миллионы книг написа- ны с помощью довольно скромного алфавита, относительно небольшой набор машинных инструкций позволяет создать бесчисленное множе- ство разных программ.
    У процессора также есть свой набор специальных переменных, кото- рые называются регистрами. Большинство инструкций пользуется

    0x250 Практическая работа
    37
    этими регистрами для чтения или записи данных, поэтому для пони- мания команд процессора нужно знать эти регистры.
    Общая картина становится еще шире...
    0x252 Процессор x86
    У процессора x86 есть несколько регистров, которые можно считать его внутренними переменными. Можно было бы порассуждать о реги- страх теоретически, но я считаю, что лучше увидеть все своими гла- зами. Среди инструментов разработчика GNU есть отладчик с именем
    GDB. Отладчик позволяет программисту пошагово выполнять ском- пилированную программу, исследовать память программы и просма- тривать регистры процессора.
    Программист, никогда не изучавший внутреннее устройство програм- мы с помощью отладчика, подобен медику XVII века, не знавшему микроскопа. Как и микроскоп, отладчик дает хакеру возможность заглянуть в машинный код, при этом отладчик гораздо более мощное орудие, чем описывает эта метафора. В отличие от микроскопа, от лад- чик позволяет рассмотреть выполнение кода с любой точки зрения и активно вмешаться в него.
    Ниже показано, как с помощью GDB выяснить состояние регистров процессора непосредственно перед началом работы программы.
    reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) break main
    Breakpoint 1 at 0x804837a
    (gdb) run
    Starting program: /home/reader/booksrc/a.out
    Breakpoint 1, 0x0804837a in main ()
    (gdb) info registers eax 0xbffff894 -1073743724
    ecx 0x48e0fe81 1222704769
    edx 0x1 1
    ebx 0xb7fd6ff4 -1208127500
    esp 0xbffff800 0xbffff800
    ebp 0xbffff808 0xbffff808
    esi 0xb8000ce0 -1207956256
    edi 0x0 0
    eip 0x804837a 0x804837a
    eflags 0x286 [ PF SF IF ]
    cs 0x73 115
    ss 0x7b 123
    ds 0x7b 123
    es 0x7b 123
    fs 0x0 0
    gs 0x33 51

    38
    0x200 Программирование
    (gdb) quit
    The program is running. Exit anyway? (y or n) y reader@hacking:/booksrc $
    На функции main() установлена точка останова (breakpoint), поэтому выполнение останавливается непосредственно перед началом нашего кода. Затем GDB запускает программу, останавливается в точке оста- нова и получает команду показать все регистры процессора и их теку- щее состояние.
    Первые четыре регистра (EAX, ECX, EDX и EBX) называются регистра- ми общего назначения. Они соответственно называются аккумулятором
    (Accumulator), счетчиком (Counter), регистром данных (Data) и базо-
    вым регистром (Base). Они служат для разных целей, но в основном в ка- честве временных переменных ЦП при выполнении машинных команд.
    Следующие четыре регистра (ESP, EBP, ESI и EDI) тоже являются реги- страми общего назначения, но иногда их называют указателями и ин- дексными регистрами. Они соответственно называются указателем
    стека (Stack Pointer), указателем базы (Base Pointer), индексом источ-
    ника (Source Index) и индексом приемника (Destination Index). Первые два регистра называются указателями, потому что хранят 32-разряд- ные адреса, фактически указывающие местоположение в памяти. Эти регистры весьма важны для выполнения программ и управления па- мятью, поэтому ниже мы рассмотрим их более подробно. Последние два регистра также формально являются указателями, обычно указываю- щими на источник и приемник, когда нужно прочитать или записать данные. Есть команды чтения и записи, в которых используются эти регистры, но обычно можно считать их простыми регистрами общего назначения.
    Регистр EIP – это указатель команды (Instruction Pointer); содержит адрес следующей команды для процессора. Как ребенок водит паль- цем по словам, которые читает, так и процессор читает все инструкции, пользуясь регистром EIP как пальцем. Понятно, что этот регистр очень важен и активно используется при отладке. В данный момент он содер- жит адрес 0x804838a.
    Оставшийся регистр флагов EFLAGS фактически содержит несколько битовых флагов, участвующих в операциях сравнения и сегментации памяти. Реальная память разбита на несколько сегментов, о чем будет сказано ниже, за этим и следят эти регистры. Обычно на них можно не обращать внимания, поскольку непосредственный доступ к ним требу- ется редко.
    0x253 Язык ассемблера
    Раз мы в этой книге используем язык ассемблера с синтаксисом Intel, нужно настроить на работу с ним наши инструменты. Внутри GDB син- таксис дезассемблирования Intel можно задать командой set disassembly

    0x250 Практическая работа
    39
    intel
    , или сокращенно set dis intel. Чтобы такая настройка действова- ла при каждом запуске GDB, поместите эту команду в файл .gdbinit, рас- положенный в вашем основном каталоге.
    reader@hacking:/booksrc $ gdb -q
    (gdb) set dis intel
    (gdb) quit reader@hacking:/booksrc $ echo “set dis intel” > /.gdbinit reader@hacking:/booksrc $ cat /.gdbinit set dis intel reader@hacking:/booksrc $
    Настроив GDB для работы с синтаксисом Intel, попробуем разобрать- ся в нем. Команды ассемблера в синтаксисе Intel обычно имеют такой вид:
    операция <приемник>, <источник>
    Значениями приемника и источника могут быть регистры, адрес па- мяти или значение. Операции обычно представляют собой интуитив- ные мнемоники: операция mov перемещает (move) значение источника в приемник, sub вычитает (subtract), inc увеличивает (increment) и так далее. Например, следующие команды перемещают значение из ESP в EBP, а потом вычитают 8 из ESP (сохраняя результат в ESP).
    8048375: 89 e5 mov ebp,esp
    8048377: 83 ec 08 sub esp,0x8
    Существуют также операции, управляющие потоком выполнения.
    Операция cmp служит для сравнения значений, а практически все опе- рации, начинающиеся с j (от jump – прыгать), осуществляют переход в другое место кода (в зависимости от результатов сравнения). В сле- дующем примере сначала 4-байтовое значение (DWORD), находящееся в EBP, уменьшенное на 4, сравнивается с 9. Следующая команда jle (от
    jump if less than or equal to – перейти, если меньше или равно) связана с результатом предыдущего сравнения. Если указанное 4-байтовое зна- чение меньше или равно 9, осуществляется переход к команде по адре- су 0x8048393. В противном случае выполняется следующая команда jmp
    (безусловный переход). То есть если 4-байтовое значение больше 9, сле- дующей будет выполняться команда с адресом 0x80483a6.
    804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4],0x9 804838f: 7e 02 jle 8048393
    8048391: eb 13 jmp 80483a6
    Эти примеры взяты из результатов проведенного выше дизассемблиро- вания, когда отладчик настроен на синтаксис Intel, поэтому восполь- зуемся отладчиком, чтобы пошагово выполнить первую программу на уровне команд ассемблера.

    40
    0x200 Программирование
    Компилятор GCC можно запустить с флагом -g, чтобы включить в про- грамму дополнительную отладочную информацию, с помощью кото- рой GDB получит доступ к исходному коду.
    reader@hacking:/booksrc $ gcc -g firstprog.c reader@hacking:/booksrc $ ls -l a.out
    -rwxr-xr-x 1 matrix users 11977 Jul 4 17:29 a.out reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/libthread_db.so.1”.
    (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) disassemble main
    Dump of assembler code for function main():
    0x08048384 : push ebp
    0x08048385 : mov ebp,esp
    0x08048387 : sub esp,0x8
    0x0804838a : and esp,0xfffffff0
    0x0804838d : mov eax,0x0
    0x08048392 : sub esp,eax
    0x08048394 : mov DWORD PTR [ebp-4],0x0
    0x0804839b : cmp DWORD PTR [ebp-4],0x9 0x0804839f : jle 0x80483a3
    0x080483a1 : jmp 0x80483b6
    0x080483a3 : mov DWORD PTR [esp],0x80484d4 0x080483aa : call 0x80482a8 <_init+56>
    0x080483af : lea eax,[ebp-4]
    0x080483b2 : inc DWORD PTR [eax]
    0x080483b4 : jmp 0x804839b
    0x080483b6 : leave
    0x080483b7 : ret
    End of assembler dump.
    (gdb) break main
    Breakpoint 1 at 0x8048394: file firstprog.c, line 6.
    (gdb) run
    Starting program: /hacking/a.out
    Breakpoint 1, main() at firstprog.c:6 6 for(i=0; i < 10; i++)
    (gdb) info register eip eip 0x8048394 0x8048394
    (gdb)

    0x250 Практическая работа
    41
    Сначала выводится исходный код и результат дизассемблирования функции main(). Затем на начало main() устанавливается точка останова и запускается программа. Точка останова сообщает отладчику, что дой- дя до нее, он должен приостановить выполнение программы. Посколь- ку точка останова установлена на начало функции main(), программа доходит до нее и останавливается перед выполнением любых команд из main()
    . Отображается значение EIP (указателя текущей команды).
    Обратите внимание: EIP содержит адрес памяти, по которому располо- жена команда из дизассемблированного кода функции main() (выделе- на полужирным начертанием). Предшествующие команды (выделены курсивом) в совокупности называются прологом функции. Компилятор генерирует их, чтобы организовать память для локальных переменных функции main(). Частично причина, по которой C требует объявлять пе- ременные, заключается в том, чтобы помочь составлению этого участка кода. Отладчику известно, что эта часть кода генерируется автомати- чески, и он достаточно сообразителен, чтобы проскочить ее. Далее мы подробно рассмотрим пролог функции, а пока воспользуемся подсказ- кой GDB и пропустим его.
    В отладчике GDB можно непосредственно изучать содержимое памя- ти с помощью команды x (от examine). Изучение памяти – важное уме- ние для любого хакера. Большинство хакерских эксплойтов напоми- нает магические фокусы: они кажутся поразительными и чудесными, пока вы не узнаете, в чем состоит ловкость рук и как отвлекается ваше внимание. И в фокусах, и в хакинге нужно смотреть в нужную точку, и тогда становится очевидным, в чем состоит фокус. Вот почему хоро- ший иллюзионист не повторяет свой фокус дважды. Но при работе с та- ким отладчиком, как GDB, любой аспект выполнения программы мож- но детерминированно изучить, приостановить, пройти пошагово и по- вторить столько раз, сколько нужно. Выполнение программы в основ- ном состоит в работе процессора с сегментами памяти, поэтому изуче- ние содержимого памяти – это первое, с чего нужно начать анализ ра- боты программы.
    Команда x отладчика позволяет просмотреть разными способами со- держимое по определенному адресу памяти. Команда принимает два аргумента: адрес памяти и формат отображения содержимого. Формат отображения также представляет собой односимвольное сокращение, которому может предшествовать число, указывающее количество ото- бражаемых элементов. Вот обозначения некоторых стандартных фор- матов:
    o
    Восьмеричный (octal).
    x
    Шестнадцатеричный (hexadecimal).
    u
    Десятичный без знака (unsigned decimal).
    t
    Двоичный (binary).

    42
    0x200 Программирование
    Эти форматы можно указывать в команде examine для изучения задан- ных адресов памяти. В следующем примере используется текущий адрес из регистра EIP. В GDB часто используются сокращенные коман- ды, и даже info register eip можно сократить до i r eip.
    (gdb) i r eip eip 0x8048384 0x8048384
    (gdb) x/o 0x8048384 0x8048384 : 077042707
    (gdb) x/x $eip
    0x8048384 : 0x00fc45c7
    (gdb) x/u $eip
    0x8048384 : 16532935
    (gdb) x/t $eip
    0x8048384 : 00000000111111000100010111000111
    (gdb)
    Память, на которую указывает регистр EIP, можно изучить, восполь- зовавшись адресом, хранящимся в EIP. Отладчик позволяет непо- средственно ссылаться на регистры, поэтому $eip эквивалентно зна- чению, содержащемуся в EIP в данный момент. Восьмеричное значе- ние 077042707 совпадает с шестнадцатеричным 0x00fc45c7, с десятичным
    16532935 и с двоичным 00000000111111000100010111000111. В команде exam- ine можно также указать число перед форматом, чтобы исследовать не- сколько блоков по целевому адресу.
    (gdb) x/2x $eip
    0x8048384 : 0x00fc45c7 0x83000000
    (gdb) x/12x $eip
    0x8048384 : 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02 0x8048394 : 0x84842404 0x01e80804 0x8dffffff 0x00fffc45 0x80483a4 : 0xc3c9e5eb 0x90909090 0x90909090 0x5de58955
    (gdb)
    По умолчанию размер одного блока равен четырем байтам, что состав- ляет одно компьютерное слово. Размер отображаемых блоков можно изменить, добавив к имени формата букву, соответствующую размеру.
    Допустимы размеры:
    b
    1 байт (byte)
    h
    Полуслово (halfword), 2 байта
    w
    Слово (word), 4 байта
    g
    Гигантское слово (giant), 8 байтов
    Это может вызвать некоторую путаницу, поскольку иногда термином
    слово обозначают двухбайтные значения. В таких случаях двойным
    словом, или DWORD, называют четырехбайтные значения. В данной кни- ге слово и DWORD относятся к четырехбайтным значениям. Двухбайтные значения я называю коротким словом (short) или полусловом. Следу-

    0x250 Практическая работа
    43
    ющий пример показывает, как выглядит вывод GDB для блоков разно- го размера.
    (gdb) x/8xb $eip
    0x8048384 : 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00 0x83
    (gdb) x/8xh $eip
    0x8048384 : 0x45c7 0x00fc 0x0000 0x8300 0xfc7d 0x7e09 0xeb02 0xc713
    (gdb) x/8xw $eip
    0x8048384 : 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02 0x8048394 : 0x84842404 0x01e80804 0x8dffffff 0x00fffc45
    (gdb)
    Внимательный читатель обнаружит в приведенных выше данных нечто странное. Первая команда examine отображает первые 8 байт, и естественно, что команды examine, где заданы большие блоки, ото- бражают больше данных. Однако первая команда examine показы- вает, что первые два байта содержат 0xc7 и 0x45, тогда как полусло- во, расположенное по тому же адресу, содержит 0x45c7 – байты в об- ратном порядке. Такой же эффект перестановки байтов наблюдает- ся в отображаемом полном четырехбайтном слове, которое выглядит как 0x00fc45c7, хотя по одному первые четыре байта выводятся в по- рядке 0xc7, 0x45, 0xfc и 0x00.
    Это вызвано тем, что процессор x86 хранит значения байтов в обрат-
    ном порядке байтов (little-endian byte order), при котором первым за- писывается младший байт. Например, если четыре байта нужно интер- претировать как одно число, байты следует считывать в обратном по- рядке. Отладчик GDB знает, в каком порядке хранятся значения, по- этому при отображении в формате слов или полуслов для правильного представления чисел в шестнадцатеричном виде нужно изменить по- рядок байтов на противоположный. Возможной путаницы можно из- бежать, отображая числа как шестнадцатеричные или как десятичные без знака.
    (gdb) x/4xb $eip
    0x8048384 : 0xc7 0x45 0xfc 0x00
    (gdb) x/4ub $eip
    0x8048384 : 199 69 252 0
    (gdb) x/1xw $eip
    0x8048384 : 0x00fc45c7
    (gdb) x/1uw $eip
    0x8048384 : 16532935
    (gdb) quit
    The program is running. Exit anyway? (y or n) y reader@hacking:/booksrc $ bc -ql
    199*(256^3) + 69*(256^2) + 252*(256^1) + 0*(256^0)
    3343252480 0*(256^3) + 252*(256^2) + 69*(256^1) + 199*(256^0)
    16532935

    44
    0x200 Программирование quit reader@hacking:/booksrc $
    Первые четыре байта выведены и в шестнадцатеричном формате, и в обычном десятичном без знака. С помощью калькулятора командной строки bc можно убедиться, что если выбрать неверный порядок байтов, получится совершенно неправильный результат 3343252480. Всегда нуж- но помнить о порядке байтов в той архитектуре, с которой вы работаете.
    Обычно средства отладки и компиляторы автоматически учитывают по- рядок байтов, но нам придется работать с памятью вручную.
    Помимо преобразования порядка байтов команда examine позволя- ет GDB осуществлять и другие преобразования. Мы уже видели, что
    GDB умеет дизассемблировать команды машинного языка в команды, понятные человеку. Команде examine можно также передать параметр формата i (от instruction), чтобы вывести содержимое памяти в виде команд ассемблера.
    reader@hacking:/booksrc $ gdb -q ./a.out
    Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
    (gdb) break main
    Breakpoint 1 at 0x8048384: file firstprog.c, line 6.
    (gdb) run
    Starting program: /home/reader/booksrc/a.out
    Breakpoint 1, main () at firstprog.c:6 6 for(i=0; i < 10; i++)
    (gdb) i r $eip eip 0x8048384 0x8048384
    (gdb) x/i $eip
    0x8048384 : mov DWORD PTR [ebp-4],0x0
    (gdb) x/3i $eip
    0x8048384 : mov DWORD PTR [ebp-4],0x0 0x804838b : cmp DWORD PTR [ebp-4],0x9 0x804838f : jle 0x8048393
    (gdb) x/7xb $eip
    0x8048384 : 0xc7 0x45 0xfc 0x00 0x00 0x00
    (gdb) x/i $eip
    0x8048384 : mov DWORD PTR [ebp-4],0x0
    (gdb)
    В приведенном листинге в GDB запущена программа a.out с точкой останова в main(). Поскольку регистр EIP содержит адрес памяти, по которому действительно располагаются команды машинного языка, они дизассемблируются в лучшем виде.
    Предшествующие результаты работы objdump подтверждают, что семь байтов, на которые указывает EIP, действительно являются машин- ным кодом соответствующих команд ассемблера.
    8048384: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-4],0x0

    0x250 Практическая работа
    1   2   3   4   5   6   7   8   9   ...   51


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