|
Хакинг. Хакинг__искусство_эксплоита_2_е_469663841. Книга дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах
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
380x200 Программирование (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 Практическая работа |
|
|