25.05.2006

Fighting 0-day.. #1: Способы борьбы с неизвестными атаками

image

Любой рынок постоянно развивается, и безопасность здесь не является исключением. Но, к сожалению, в нашей стране (да и в остальном мире, но в меньшей степени, я думаю..) забывают о реальном обеспечении защиты сводя все к покупке продуктов вроде файрволла или очередной VPN-системы, а также созданию политик безопасности и регламентов..

Лакехин Станислав, специалист компании Mobile Intelligence Agency, lakehin@miag.ru

Введение

В наше время информационная безопасность является одним из ключевых аспектов, необходимых для непрерывного и успешного ведения современного бизнеса. Рынок информационной безопасности как в нашей стране, так и во всем мире очень быстро и успешно развивается в нескольких различных направлениях. Как правило, это либо разработка специального ПО для защиты (например файрволлы, VPN-системы, антивирусы,  SPAM-фильтры и пр.), либо предоставление специализированных услуг (вроде анализа рисков компании и разработки политики информационной безопасности). Но, к сожалению, вся индустрия очень часто забывает о такой вещи, как 0-day уязвимости и приватные эксплоиты, которые постоянно появляются в соответствующих

неофициальных кругах, а также используются и покупаются уже более заинтересованными лицами. В первой части моего материла я хотел бы ввести вас в курс дела, описав основные фундаментальные атаки на компьютерные системы, а также способы обхода защит, созданных специалистами по безопасности.

Линия Фронта

Итак, давайте определимся, какие атаки представляют собой главную опасность для программного обеспечения (и разумеется для ОС, на которой установлено данное ПО) - это атаки переполнения буффера (в стеке и хипе), ошибки форматной строки, целочисленные переполнения, а также другие уязвимости, которые мы опишем подробнее

в следующих разделах. Это те вещи, которые грамотные разработчики постоянно ищут в своем коде, это то, что является причиной 99% всех Advisory (уведомлений, касающихся безопасности) - те патчи, которые приходиться устанавливать системным администраторам чуть ли не каждый день. Но предположим такой вариант, что хакер нашел уязвимость раньше разработчика (такое случается постоянно, хотя бы потому, что у разработчика полно других забот). Как правило, он разрабатывает эксплоит - специальный код, которых использует конкретную уязвимость. По вашему этот эксплоит он немедленно отправит в BugTraq и разработчики, проанализировав код, выпустят соответствующую заплатку? Уверяю вас, времена full-disclosure (полного разглашения информации) уже давно прошли, как и времена различных security-team'ов, которые занимались подобной практикой. Те, кто остались, либо ушли на работу в коммерческие организации, либо в глубокий андеграунд.. Как правило, первые не занимаются подобными вещами, а вот вторые не только продают подобный "warez" в чистом виде, но и активно его используют (к примеру для создания ботнетов, организации DDoS атак, рассылки спама, а также для других более утонченных и комплексных проектов). И увы, тут не помогут не новейшие файрволлы и антивирусы, ни системы анализа защищенности - от подобных атак не могут защитить стандартные средства, здесь нужен другой подход. Но прежде чем обсуждать возможные варианты защиты, давайте подробнее остановимся на этих атаках.

Stack Overflow

Это самый старый и известный метод, котором пользуются люди, которые ищут ошибки в ПО и пишут эксплоиты. Проблема состоит в функциях, которые не проверяют границ (например strcpy(), gets(), strcat(), scanf(), fscanf(), getwd() и пр, если применительно к C/C++). Из за этого мы можем выйти за границы массива (группа данных идентичного типа) и изменить важные значения, сохраненные в стеке, в том числе EIP - адрес возврата (это место, куда программа передает управление после выхода из функции) скажем на адрес нашего шеллкода (машинный код, которые выполняет нужные нам действия, чаще всего это запуск интерпретатора командной строки). Итак, давайте на примере посмотрим как данные некой функции располагаются в стеке, используя классический strcpy баг:

-- stack.c
char *vuln(char *msg) {
int int1;
char buf[80];
int int2;
strcpy(buf,msg);
return msg;
}
int main (int argv, char **argc[]) {
char *ptr;
ptr = vuln (argc[1]); }
-- EOF

После загрузки программы в виртуальную память, стек будет выглядеть так:

-------------------------------------------------
start()/main() | аргументы main() | 12 байтов
main() | сохраненный eip | 4 байта
main() | сохраненный ebp | 4 байта
main() | ptr | 4 байта
main()/vuln() | аргументы vuln() | 4 байта
vuln() | сохраненный eip | 4 байта
vuln() | сохраненный ebp | 4 байта
vuln() | int1 | 4 байта
vuln() | buf | 80 байтов
vuln() | int2 | 4 байта
-------------------------------------------------

Итак, мы контролируем msg, буффер buf фиксированного размера (80 байтов, но в реальности компилятор может выделить немного больше пространства). Функция strcpy не проверяет границ, поэтому мы можем переписать все за buf, включая сохраненный адрес возврата в стеке, как это видно на схеме. Проверим:

MiagBox:/home/stas/samag# gcc -o stack stack.c
stack.c: In function `main':
stack.c:10: warning: passing arg 1 of `vuln' from incompatible pointer type
stack.c:10:22: warning: no newline at end of file
MiagBox:/home/stas/samag# gdb ./stack
GNU gdb 6.3-debian
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-linux"...
Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) r `perl -e 'print "A"x256'`
Starting program: /home/stas/stack `perl -e 'print "A"x256'`

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

Как видите, программа пыталась выполнить инструкцию по несуществующему адресу 0x41414141 (41 in hex = A), то есть мы полностью контролируем значение адреса возврата (регистра EIP) и можем заставить программу выполнить любые действия.

Конечно, существуют различные вариации на тему написания эксплоитов переполнения буффера, так как методы эксплоитинга stack-based переполнений очень сильно эволюционировали (nops+sc, eggshell, Murat Balaban method, ret-to-func и тд.). Но для нас, как правило, это не играет большой роли.

Heap Overflow

Данные атаки не так хорошо изучены и описаны, по сравнению со stack overflow, но, тем не менее, широко распространены. Для хранения очень больших объемов данных часто используют heap - специальную область памяти, которая, в отличие от стека, растет вверх. В heap-based переполнении мы можем переписать указатели (например, указатель на файл - подставить туда свое значение, допустим /etc/shadow и если процесс запущен под рутом, то мы увидим содержимое этого файла), указатели на функции (к примеру, заменить адрес функции на адрес нашего шеллкода) и пр. Проблема все та же - использование небезопасных функций, отсутствие проверки границ, исполняемый heap.. Рассмотрим простой пример heap-based переполнения (используется Doug Lea's Malloc) :

-- heap.c
#include
int main(int argc, char **argv) {
char *ptr,buff[10];
ptr=malloc(buff);
strcpy(buff,argv[1]);
free(ptr); }
-- EOF
MiagBox:/home/stas/samag# gcc -o heap heap.c
heap.c: In function `main':
heap.c:5: warning: assignment makes pointer from integer without a cast
heap.c:7:13: warning: no newline at end of file
MiagBox:/home/stas/samag# gdb ./heap
GNU gdb 6.3-debian
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-linux"...Using host libthread_db library
"/lib/tls/libthread_db.so.1".

(gdb) r `perl -e 'print "A"x256'`
Starting program: /home/stas/samag/heap `perl -e 'print "A"x256'`

Program received signal SIGSEGV, Segmentation fault.
0x40086ac9 in free () from /lib/tls/libc.so.6
(gdb)

У нас не получается стандартного переполнения, так как происходит вызов функции free(), которая пытается освободить память по адресу 0x41414141, естественно такого адреса просто не существует и программа успешно падает. Чтобы обмануть free(), нам надо просто подставить ей любой валидный адрес в heap пространстве, проверим:

(gdb) r `perl -e 'print "A"x28'``printf "\xff\x84\x04\x08"``perl -e 'print "A"x28'`
Starting program: /home/stas/samag/heap `perl -e 'print "A"x28'``
printf "\xff\x84\x04\x08"``perl -e 'print "A"x28'`

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

Мы передали free() адрес в хипе, который она успешно освободила (к слову сказать, это адрес ptr в heap - во избежание лишних проблем мы передали именно этот адрес), и после выхода из функции соответственно передала управление адресу, который мы полностью контролируем. Есть еще очень много различных способов, вариантов и возможностей использования и эксплоитирования heap-based переполнений. Эти ошибки довольно распространены и дополнительная (и наверное, главная) сложность состоит в том, что такие ошибки очень сложно отлавливать на уровне кода - то есть, потенциально, их очень много.

Format Strings

Ошибки данного класса появились из-за особенностей реализации функций семейства printf (и им подобных). Проблема в том, что если не указан спецификатор формата, пользователь может сам его указать, а значения система будет брать из стека (с вершины). Более того, есть спецификатор %n, который позволяет записать определенное значение по заданному адресу (что и позволяет нам выполнять произвольный код)! Рассмотрим небольшой пример уязвимой программы:

-- test.c
#include
int main(int argc, char *argv[]) {
char stack[5]="info", fmtbug[10];
strncpy(fmtbug,argv[1],sizeof(fmtbug));
printf(fmtbug); /* Fomrat String Bug! */
printf("\n");
}
-- EOF
MiagBox:/home/stas/samag# gcc -o test test.c
test.c:8:2: warning: no newline at end of file
MiagBox:/home/stas/samag# ./test data
data
Итак, попробуем получить данные из стека:
MiagBox:/home/stas/samag# ./test "data %x %x"
data bffffbda aЪїJinfo

Как видите, мы добрались до массива stack.

При определенных условиях, мы можем изменить процесс выполнения

программы, то есть заставить ее выполнить некий условный код по

нашему адресу, используя все тот же спецификатор "%n", цель стандартная -

перевести управление на наш вредоносный код. Про "банальный" DoS ala "%s" x 100 я упоминать не буду. Однако успокаивает то, что подобные ошибки довольно легко отлавливать автоматизированными средствами, но это не касается функций, которые написали программисты специально для своего ПО..

Integer Overflow

Это самый новый способ из описываемых мною фундаментальных атак, который очень

быстро набирает обороты. Переменные с типом int - это область в памяти (как правило, 32 или 64 бита), предназначенная для хранения некого целочисленного значения. Целочисленное переполнение - это выход за пределы допустимого значения, если в переполнении к примеру стека играет роль размер массива, то здесь имеет значение максимальное число, которое может храниться в переменной. В некоторых случаях мы можем  влиять на процесс выполнения программы. Давайте рассмотрим пример:

-- int.c
#include
#include
int main(int argc, char *argv[]) {
unsigned short a;
int b = atoi(argv[1]);
char c[100];
a=b;
/* protection */
if (a >= 100) {
printf("kind of protection.. Exiting\n");
return 0;
}
memcpy(c,argv[2],b);
printf("%s\n",c);
return 0;
}
-- EOF

На первый взгляд программа должна работать нормально, проверим:

MiagBox:/home/stas/samag# gcc -o int int.c
MiagBox:/home/stas/samag# ./int 3 testmessage
tes

Но что будет, если мы попытаемся переполнить i ?

MiagBox:/home/stas/samag# ./int 65536 testmessage        
Segmentation fault

Дело в том, что размер аргумента копируется в b, а потом "переносится"

в a, в котором не хватает места для числа (тип short). Из-за этого мы

проходим проверку. После этого могут быть использованы стандартные методы (вроде

переполнения стека). И хотя целочисленные переполнения в чистом виде почти не

предоставляют угрозы, тем не менее, они иногда позволяют провести другие атаки, вроде переполнения стека или использование ошибок форматной строки. Хотелось бы заметить, что сейчас появились способы детектирования целочисленного переполнения. Возможно, в скором времени подобные проверки будут встроены в компиляторы.

Other techniques and code problems..

Существуют также несколько других общеизвестных проблем и техник, и я вкратце опишу их.

Return-to-Func (Return-into-Libc) - как правило, используется для обхода систем с  неисполняемым (non-executable) стеком. Суть работы довольно проста - поскольку мы не можем выполнить код в стеке, мы "возвращаемся" не в стек, а в область памяти, используемую общими библиотеками. К примеру, мы можем переписать EIP на адрес функции system(), передав дальше аргументы (/bin/sh, например). Поскольку никакого кода в самом стеке не будет выполняться, мы успешно получаем оболочку (если переписываем на system() и указателем на /bin/sh в качестве  аргумента, разумеется). С этим как правило борются наличием null-байтов в адресах функций, а также рандомизацией их адресов (подобные способы реализованы в некоторых средствах защиты).

Frame pointer overflow - это атака, при которой мы можем переполнить наш буффер всего лишь на один байт. При определенных условиях мы можем выполнить произвольный код. Идея состоит в том, что мы в состоянии переписать 1 байт в EBP. После выхода из функции регистр EBP будет скопирован в ESP, то есть мы можем изменять регистр произвольным значением (не совсем произвольным, так как нам доступен для перезаписи только один байт). Итак, вот "псевдокод", это конец функции main():

0x81253b1 :    movl   %ebp,%esp
0x81253b3 : popl %ebp
0x81253b4 : ret
0x81253b5 : nop
0x81253b6 : nop

То есть программа копирует EBP в ESP и выполняет инструкции по адресу, который находится в стеке (ret) - на что указывает ESP.

Adj overflow (adjancent memory overflow) - это последствия особенностей

реализации некоторых функций. Многие функции находят конец строки

по null байту (0x00), но что если строка у нас такого же размера, как и буффер?

NULL-байт просто не будет записан! Рассмотрим пример:

-- adj.c
#include
int main (int argc[], char *argv[]) {
char buff1[263];
char buff2[1024];
char buff3[256];
strncpy(buff3, argv[1], sizeof(buff3));
strncpy(buff2, argv[2], sizeof(buff2));
sprintf(buff1, "%s", buff3);
}
-- EOF

Итак, вопрос: Может ли buff3 переполнить buff1? Может! Если

buff3 не будет завершен NULL байтом, то sprintf будет продолжать

копирование, пока не встретит этот null-byte, а вот и последствия:

MiagBox:/home/stas/samag# gcc -o adj adj.c
MiagBox:/home/stas/samag# gdb ./adj
GNU gdb 6.3-debian
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-linux"...
Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) r `perl -e 'print "A"x240'` `perl -e 'print "B"x125'`
/* Немного меньше нашего буффера, "блокирует" strcpy.. */
Starting program: /home/stas/samag/adj `perl -e 'print "A"x240'` `perl -e 'print "B"x125'`

Program exited with code 0360.
(gdb) r `perl -e 'print "A"x250'` `perl -e 'print "B"x125'`
/* Немного больше нашего буффера, "блокирует" strcpy.. */
Starting program: /home/stas/samag/adj `perl -e 'print "A"x250'` `perl -e 'print "B"x125'`

Program exited with code 0372.
(gdb) r `perl -e 'print "A"x256'` `perl -e 'print "B"x125'`
/* oops.. это равно размеру нашего буффера */
Starting program: /home/stas/samag/adj `perl -e 'print "A"x256'` `perl -e 'print "B"x125'`

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb)

Мы можем изменить процесс выполнения программы.. Как видите, при определенных условиях так можно эксплоитировать функцию strncpy ("безопасная" замена strcpy, с проверкой границ) и не только ее. Однако стоит отметить, что данная атака требует множество условий.

Shellcoding

Как я уже говорил, шеллкод - это машинный код, как правило локально вызывающий

оболочку или привязывающий ее к сокету (используется для удаленных

эксплоитов). Есть еще много различных типов шеллкодов, это reverse-conected шеллкоды (как правило, используются для обхода файрволлов и других фильтров сетевого траффика), полиморфные шеллкоды (используются для обхода систем обнаружения вторжений), architecture spanning шеллкоды (это машинный код, который будет успешно работать на различных архитектурах, например на SPARC и на Intel, это повышает универсальность шеллкода), alphanumeric и unicode шеллкоды (используются для обхода фильтров на различные символы в приложениях) и тд, все это очень большая, интересная и сложная тема. Вот пример простого execve-шеллкода (под ОС Linux):

        "\x31\xc0"                      // xor    %eax,%eax
"\x50" // push %eax
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f
"\x89\xe3" // mov %esp,%ebx
"\x8d\x54\x24\x08" // lea 0x8(%esp,1),%edx
"\x50" // push %eax
"\x53" // push %ebx
"\x8d\x0c\x24" // lea (%esp,1),%ecx
"\xb0\x0b" // mov $0xb,%al
"\xcd\x80" // int $0x80

Данный машинный код выполняет /bin/sh (linux x86). Тонкости работы кода нас не сильно интересуют, нам лишь важно знать, что шеллкод используется в 95% эксплоитов, и при удачной атаке на него передается управление и он, разумеется, выполняется. Работа шеллкода составляет главную и ключевую роль практически в любом эксплоите. Вот вы и познакомились с фундаментальными атаками, теперь давайте немного рассмотрим системы защиты, созданные специалистами по безопасности.

Разработанные Методы Защиты

С момента появления и активного развития фундаментальных атак прошло очень много времени, разработчики защиты придумали не мало средств, которые сильно осложнили жизнь атакующим. Но, к сожалению, практически во всех методы защиты были найдены уязвимости, в том числе архитектурные. Итак, сначала я решил разделить защитные механизмы на различные группы, вроде "non-executable stack" и "canary protection", но потом решил отдельно описать некоторые защитные средства, поскольку многие из них комбинируют используемые техники.

StackGuard / StackShield

Суть работы этих программ довольно проста - StackGuard записывает canary (специальное

псевдослучайное значение) между сохраненным адресом возврата и

EBP. Если значение canary изменилось, то это событие квалифицируется как атака на переполнение буффера. StackShield действует иначе - он создает специальное пространство retarray для хранения копии адреса возврата. После выхода из функции этот адрес копируется в EIP. Каким бы безопасным это не казалось на первый взгляд, существует по крайней мере несколько способов обхода данных средств защиты. Во первых, нам не обязательно переписывать canary, если мы можем воздействовать на указатель - переписать его на адрес возврата, а потом попросту заменить его на шеллкод, то есть если есть такой псевдокод:

strcpy(a,userbuf)
strncpy(a,userbuf2,16)

Естественно, предполагается что "a" - контролируемый нами указатель.

Хотя, конечно, успех атаки зависит от кода функции, что естественно

очень сильно снижает риски.. Но есть другие способы, например,

немного изменить Global Offset Table (GOT), скажем, вот в таком коде:

printf ("a=%x > This is 1st strcpy\n",a);
strcpy(a,argv[1]);
printf ("a=%x > After 1st strcpy\n",a);
strncpy(a,argv[2],16);
printf("and after second strcpy..\n");

нам надо переписать GOT printf() на адрес system(), в результате программа

выполнит system("and after second strcpy..\n");

и все что нам теперь нужно - это создать скрипт "and" в нашей директории,

который и запустит программа.. Или, например, если программа вызывает функцию exit() (разумеется, у нас по прежнему должен иметься в наличии указатель, которым мы можем манипулировать), мы можем переписать адреса функций в структуре fnlist на адрес нашего шеллкода. Это лишь часть найденных способов обхода подобных защит. Данные программы сейчас уже практически нигде не используются, но надо отдать им должное, так как многие современные системы защиты используют представленные авторами этих продуктов техники.

Windows 2003

В операционной системы Microsoft Windows 2003 встроена защита от переполнения стека.

Она заключается в следующем - за адресом возврата (который сохраняется в стеке, чтобы программа знала куда обращаться после выхода из функции) кладется специальное число (canary, cookie..). Если мы попытаемся переписать адрес возврата, соответственно, изменим canary и выполнение процесса прекратится (вы не находите сходства со StackGuard?). Но существует несколько способов обхода подобной защиты, нацеленных не на архитектурную составляющую защиты в целом и не на особенности реализации уязвимых функций в эксплуатируемом ПО, а именно на ее реализацию специалистами софтверного гиганта. Если cookie не совпадает с оригинальным значением, который хранится в секции .data, то выполняется специальный security-handler, который тоже хранится в секции .data. Если никакой обработчик (handler) не задан, тогда UnhandledExceptionFilter устанавливается на 0x00000000 и выполняется функция UnhandledExceptionFilter - которая в свою очередь загружает в общую память библиотеку faultrep.dll и вызывает функцию ReportFault (это то самое всплывающее окно, которое просит вас отправить отчет о ошибке в Microsoft). Стоит заметить, что в ОС Windows существуют обработчики исключений, которые помогают программистам делать приложения более стабильными, если происходит какое-то непредвиденное действие, например попытка записи в read-only память, для такого действия может быть написано исключение, которое в последствии может "реанимировать" процесс. Дело в том, что в каждом потоке процесса существует как минимум один обработчик, информация о нем хранится в стеке, в структуре EXCEPTION_REGISTRATION, которая имеет два элемента - указатель на следующую структуру EXCEPTION_REGISTRATION и указатель на обработчик исключений, который мы не можем просто переписать - так как теперь все адреса обработчиков исключений зарегистрированы и хранятся в Load Configuration Directory. То есть, прежде чем выполнить обработчик по некому адресу, он сверяется со списком зарегистрированных обработчиков и не будет выполнен, если не будет там найден. Но стоит заметить, если адрес обработчика находится за пределами адресного пространства загруженного модуля, то это адрес БУДЕТ выполнен! Также если адрес находится в области heap - он будет выполнен тоже, хотя, если адрес будет в области стека, по понятным причинам он выполнен не будет. Итак, нам надо сгенерировать исключение, например, попытаться что-то записать за пределами стека.

Это вызовет исключение. Теперь остается только переписать указатель на обработчик

адресом вне адресного пространства загруженного модуля, он будет проверен со списком зарегистрированных исключений, но поскольку адрес находится вне адресного пространства, ему будет передано управление. Теперь атакующему остается лишь найти блок инструкций вне адресного пространства, который переведет управление на область в памяти, которую контролирует атакующий (то есть в область, в которую он может "загрузить" шеллкод). Как вариант можно еще (если это позволяет эксплуатируемый код) "загрузить" шеллкод в буффер, который находится в хипе (heap) и переписать указатель обработчика на адрес буффера в heap пространстве.

Windows XP Service Pack 2

С течением времени Microsoft решила усовершенствовать свою защиту, обратив

внимание на область хипа (heap). Однако, к сожалению, надежды не увенчались успехом

(увы, но такое часто случается..). Существует как минимум несколько различных способов противодействия защите, встроенной в пакет обновлений Service Pack 2 операционной системы Windows XP. Итак, чтобы обезопасить себя от переполнений, в систему менеджмента хипа было добавлено две проверки чанков (chunk's) - во первых, это проверка cookie в заголовке чанка, а во вторых, довольно эффективная проверка указателей Flink и Blink (это указатели на следующий и предыдущий свободные блоки соответственно). На мой взгляд, наиболее предпочтительный метод обхода защиты заключается в следующем - многие API-функции хранят некоторые данные загруженных библиотек (DLL) в хипе, то есть еще до старта main(), в хипе уже создано несколько чанков, как правило их структура выглядит так:

------------------
| chunk header |
------------------
| 0 X |
| A B |
| 0 0 |
| ? ? |
------------------

где A и B - адреса следующей и предыдущей структур соответственно и если мы сможем их заменить, то будем в состоянии переписать произвольные 4 байта в памяти (например, адрес возврата). Эта атака возможна потому, что не происходит никакой проверки указателей на структуры (A и B)! Также существует другой метод, который предложил наш соотечественник, Александр Анисимов, связанный с атакой на ассоциативные массивы, так как в них тоже не проводится никаких специальных проверок, но такой метод довольно сложно применить на практике. Последствия - возможность переписать произвольные 1016 байтов в памяти.

Cisco Security Agent / NAI Entercept (stack backtracing)

Рынок информационной безопасности потихоньку стал немного присматриваться

к защите от переполнения буффера, встраивая защитные механизмы в свои HIPS (Host Intrusion Prevention Systems), которые, помимо всех прочих возможностей (противодействие spyware/adware, встроенный файрволл, аудит различных событий и пр), содержат так называемую технологию Stack Backtracing, которая позволяет проводить перехват и анализ функций на наличие шеллкода и соответственно запрещать вызовы при наличии такового (как ни странно, в этом содержится своя слабость -

эти продукты не предотвращают атаки переполнения буффера в принципе, то есть не создают неисполняемые стек и хип, а лишь пытаются перехватить шеллкод в вызываемых (читай перехватываемых) API-функциях). Первая проблема состоит в том, что шеллкод может детектировать функции, которые перехватываются. Защитные механизмы в обычную преамбулу функции (push ebp; mov ebp, esp) добавляют call или jmp и для шеллкода не составит труда обнаружить данную "сигнатуру". После успешного детектирования шеллкод может обойти систему защиты, например, вставить свою копию преамбулы, а потом перевести управление сразу после API-хука. Но самый универсальный способ, который действует как против перехвата функций на уровне пользователя (ring3), так и на уровне ядра (ring0) - это подделка stack фреймов, то есть шеллкоду надо создать поддельный stack фрейм без ebp в стеке. А поскольку техника stack backtracing зависит от наличия ebp в стеке, чтобы найти следующий stack-фрейм, поддельный фрейм может остановить stack backtracing. Итак, нам всего лишь надо создать stack-frame с верным адресом возврата, который указывает на read-only область памяти.

Кроме того, надо заметить, что перехват API-функций на уровне пользователя несет в себе кучу проблем. Во первых, необходимо перехватывать и анализировать ВСЕ функции, которые может использовать атакующий (например, NAI Entercept 4.1 перехватывает LoadLibraryA, но ничего не знает о LoadLibraryW). Во вторых, некоторые продукты перехватывают функции только в kernel32.dll, забывая при этом о ntdll.dll, это недоработка все в том же NAI Entercept. Также существуют способы обхода многих других средств защиты, например Open Wall (патч нашего соотечественника Александра Песляка, более известного как Solar Designer), GrSecurity, обход DEP (как софтверного, так и хардварного) и тд. Я думаю, достаточно. Главное, чтобы вы уловили суть и идеи атак и защитных механизмов, а также некоторые способы их обхода (некоторые продукты уже устранили описанные уязвимости, некоторые нет..).

Общие Принципы Защиты

Итак, после всего прочитанного вы твердо решили серьезно повысить безопасность своих систем. С чего начать? Я выделил несколько этапов активной защиты. Во первых, полезная нагрузка эксплоита (payload) пройдет множество систем по определенному маршруту. Поэтому очень важно, чтобы наш сервер "прикрывал" пограничный маршрутизатор и/или файрволл с установленной и правильно настроенной системой обнаружения/предотвращения атак (а еще лучше, если подобная система будет стоять на самом "target" хосте, если это позволяет производительность машины). Давайте подробнее рассмотрим методы защиты, которые применяются в современных системах обнаружения вторжений. Главный бастион - это сигнатуры (определенная последовательность байтов в пакете, которая соответствует некоторому действию, например, попытки эксплоитации RADIUS ATTR_TYPE_STR). Многие IDS имеют базу данных сигнатур, которые включают в себя детектирование совершенно различных действий - сканирование портов, попытки определения операционной системы (OS fingerprinting), попытки DDoS'а и пр. Но самое интересное для нас - это блокирование атак и детектирование/перехват шеллкодов. Эксплоиты (разумеется, уже существующие) перехватываются по определенным особенностям. Вот, например, пример сигнатуры для эксплоита под kadmind у IDS Snort:

alert tcp $EXTERNAL_NET any -> $HOME_NET 751 (msg:"EXPLOIT kadmind buffer overflow attempt"; flow:established,to_server; content:"|00 C0 05 08 00 C0 05 08 00 C0 05 08 00 C0 05 08|"; reference:bugtraq,5731; reference:bugtraq,6024; reference:cve,2002-1226; reference:cve,2002-1235; reference:url,www.kb.cert.org/vuls/id/875073; classtype: shellcode detect; sid:1895; rev:8

Это означает, что если tcp-пакет с любым адресом источника попытается достучаться до IP-адресов, которые содержатся в $HOME_NET по 751 порту и будет содержать в себе определенный объектный код, который представлен в шестнадцатеричном виде, то система обнаружения вторжений квалифицирует данное событие как попытку эксплоитинга BOF в kadmind. Хотя, на мой взгляд, блокирование эксплоитов по сигнатурам приносит сомнительную пользу (лишняя трата времени, не находите? Хотя..).. Более интересная часть - это детектирование и перехват шеллкодов. Достигается это теми же сигнатурами, которые содержат в себе часто используемые шеллкодами байты и функции (последовательности байтов). К примеру, все в том же IDS Snort есть препроцессор fnord, который даже пытается определить полиморфные шеллкоды.


Самый примитивный метод - определять шеллкод по наличию NOP, то есть, разумеется, не просто 0x90 (это No Operation для x86), а аналогичные по функционалу операции вроде inc/dec, xchg %ebp,%ebp и пр, а также вариации nop-ов для различных микро-процессоров. Можно "вытягивать" системные вызовы из траффика, например setuid/setgid. Кроме того, есть много довольно интересных дополнительных техник, которые активно используются некоторыми системами обнаружения/предотвращения вторжений. Например, детектирование типичных приглашений интерпретаторов командой строки (/bin/sh и cmd.exe). Не стоит и говорить, что скажем перехват шеллкодов по различным "nop-like" сигнатурам очень серьезно влияет на производительность машины. Конечно, в любом случае надо пристально изучать используемую вами IDS систему, а лучше предварительно проконсультироваться с поставщиком (главное, чтобы консультацию проводили грамотные специалисты, а то в наше время могут преподнести "перехват" с десяток не актуальных экплоитов по сигнатурам как 100% защиту от вредоносного кода.. это печально, но вопрос здесь скорее к менеджерам компаний-распространителей IDS систем). Второй (и бесспорно самый главный) бастион защиты - это используемая вами локальная операционная система. Здесь надо рассматривать несколько возможностей атаки. Мы сразу обратим внимание как на локальные атаки, то есть когда у злоумышленника уже есть валидный аккаунт, так и на удаленные атаки, учитывая, просто к примеру, что использовался какой-нибудь метод полиморфизма и шеллкод все таки прошел через IDS/IPS фильтр. Давайте по порядку остановимся на каждой атаке и составим необходимые общие рекомендации по защите. Эксплоитирование уязвимости типа переполнения буффера в стеке (Stack Based Overflow) поможет предотвратить неисполняемый стек, который сделает невозможным выполнение шеллкода (да и любого другого кода) в стеке. Хотя, некоторым приложениям это необходимо, но системы защиты придумали различные ухищрения, чтобы бороться с этим ограничением. Но существует другая проблема - ret-to-func (ret-into-libc) эксплоиты, которые с легкостью обходят подобную защиту, вызывая функции из общей памяти и подставляя соответствующие аргументы. Но их проблема в том, что они используют определенные "зашитые" адреса функций (вообще использование каких-то определенных значений понижает универсальность эксплоита, но во многих случаях это необходимо, точнее неизбежно..). Специалисты по безопасности придумали средство против данной атаки - рандомизация адресов функций, то есть при каждом новом запуске приложения адреса функций в памяти будут меняться, плюс они могут содержать часто недопустимые байты, например null-byte (такая техника используется к примеру в дистрибутиве Open Wall GNU/Linux). С переполнениями в хипе (Heap Based Overflows) как правило борются, создавая неисполняемый хип, который предотвращает выполнение любого кода в данной области памяти (non-executable heap также содержится в некоторых средствах защиты, по моим данным PaX Team впервые разработала такой модуль), а также часто модифицируют процедуру менеджмента хипа, добавляя различные проверки чанков (на валидность cookie, например). С атаками на ошибки форматной строки (format string) дела обстоят немного иначе, как вариант можно перехватывать вызовы уязвимых функций к библиотекам (например, syslog(), printf(), snprintf() и пр, которые подвержены атакам форматной строки). По поводу шеллкодов, как вы уже заметили, практически все эксплоиты используют шеллкод (ну, конечно, существуют исключения, к примеру мы можем передать управление не нашему коду, а какой-нибудь другой функции, уже заложенной в программе, но это большая редкость). Шеллкод возможно перехватывать не только на уровне сети, но и на уровне хоста. Для этих целей существуют HIPS (Host Intrusion Prevention System) - специальное ПО, которое работает на уровне хоста и перехватывает функции, анализирую их на наличие шеллкода. Проще говоря, это уже рассматриваемая нами технология stack backtracing. Конечно, существуют некоторые способы обхода данной технологии, но не надо забывать, что 99% эксплоитов используют стандартные шеллкоды и методы эксплоитирования, которые никоим образом не предназначены для тюнингованных специализированной защитой систем (не говоря уже о том, что большинство людей, использующих эксплоит, часто понятия не имеют как он работает, не говоря уже о его модификации для обхода защитных средств). Если рассматривать другие атаки, такие как целочисленное переполнение (integer overflow), adjancent memory overflow и frame pointer overwrite, то для защищаемых средств они не играют большой роли, поскольку подобные атаки позволяют лишь проэксплоитировать вышеуказанные уязвимости в коде.

Полезные Рекомендации

По большому счету существует 2 метода нападения на систему - это либо автоматизированные средства, либо "прямые" действия самого атакующего. Но для нас это не играет большой роли, так как payload от этого не меняется. Но стоит заметить, что даже mass/auto rooting редко происходит без получения какой-либо информации о атакуемой системе, не говоря уже о "manual" взломе, то есть пытаются определить хотя бы версию демона, так как нет никакого смысла атаковать Apache 1.3.26 если стоит IIS/6.0. Анализ баннера в большинстве случаев – это самый актуальный способ анализа (возможен также анализ по fingerprints демонов/серверов для более точного определения версий и вообще принадлежности к конкретной линейки продуктов, но я думаю, что такая ситуация для автоматизированных средств в реальности маловероятна..). Так что тут встает еще одна линия обороны - это скрытие информации от потенциального злоумышленника или его автоматизированного средства. Security-сообщество пытается всех убедить что "Security through obscurity" не приносит реальной безопасности. Отчасти это так, но к примеру, если мне удастся убедить атакующего, что на моей машине стоит скажем FreeBSD, а не Linux, то шеллкод, который будет использовать злоумышленник, не будет работать на моей системе, даже если и получит управление. На мой взгляд надо по возможности прятать все, что только можно. Менять параметры, которые критичны для разлиных fingerprint-техник, менять системные баннеры и другие информационные заголовки, по которым можно определить версию демона/сервиса и тд. Одним словом вводить в заблуждение атакующего или его автоматизированные средства. Более того, не забудьте грамотно настроить свой файрволл, блокируйте неиспользуемые порты (это защитит вас от bind-шеллкодов), контролируйте установку внешних соединений (это должно защитить вас от reverse-connected шеллкодов, то есть когда ваша машина сама соединяется с компьютером взломщика). Не забываейте также о Chroot и разделении привилегий - это очень действенные меры по противодействию атакам (скажем "порутанный" apache сбрасывает нам nobody shell, а если в /tmp запрещено выполнение программ, ситуация незавидная для атакующего, даже без учета chroot). Если в chroot не будет "/bin/sh" - то это просто отлично (98% unix-like шеллкодов выполняют оболочку по этому адресу). Естественно, здесь я даже не устану упоминать о таких вещах, как выбор стойких паролей, правильная установка прав доступа, постоянная проверерка лог-файлов, контроль за действиями пользователей, безопасная настройка демонов и тд, так как все это подробно описано во многих книгах и учебниках по информационной безопасности.

Во второй части своего материала я расскажу более подробно о практических методах защиты популярных операционных систем от 0-day, перехвате шеллкодов и способах обхода сигнатур IDS.

Заключение

Любой рынок постоянно развивается, и безопасность здесь не является исключением. Но, к сожалению, в нашей стране (да и в остальном мире, но в меньшей степени, я думаю..) забывают о реальном обеспечении защиты сводя все к покупке продуктов вроде файрволла или очередной VPN-системы, а также созданию политик безопасности и регламентов.. Только грамотная служба безопасности, либо действительно квалифицированные администраторы, которые разбираются в тонкостях безопасности вверенных им систем и сетевых устройств, могут обеспечить очень хорошую и реальную защиту предприятия от подобных атак! Но часто не того, не другого в организациях попросту нет, что естественно печально, и если нет возможности нанять квалифицированных специалистов по информационной безопасности, а обеспечивать реальную, а не "бумажную" защиту все же надо, я надеюсь, что моя статья помогла вам продвинуться в этом вопросе. И я также надеюсь, что этот материал заставит вас задуматься над проблемами, которые принес нам рынок 0-day, развивающийся молниеносными темпами и с каждым годом получая все больше и больше финансовых вложений от "заинтересованных лиц", которых, поверьте на слово, совсем не интересует обеспечение безопасности. Вот и все, что я хотел сказать в первой части материала. Ваши пожелания, дополнения, вопросы и замечания можно направлять по адресу lakehin@miag.ru. Более подробную информацию вы всегда сможете найти на сайте http://miag.ru.

Ссылки

  • http://miag.ru - Mobile Intelligence Agency
  • http://www.phrack.org/phrack/49/P49-14 - Smashing The Stack For Fun And Profit, by Aleph1
  • http://lbyte.ru/txt/misc/cabzz.txt - Modern kinds of system attacks, by xCrZx (Lbyte Team)
  • http://koti.welho.com/vskytta/formatstring-1.2.pdf - Exploiting Format String Vulnerabilities, by Scut (Team Teso)
  • http://www.phrack.org/phrack/60/p60-0x0a.txt - Basic Integer Overflows, by Blexim
  • http://www.phrack.org/phrack/56/p56-0x0e - TAKING ADVANTAGE OF NON-TERMINATED ADJACENT MEMORY SPACES, by twitch
  • http://www.phrack.org/show.php?pU&a=8 - The Frame Pointer Overwrite, by klog
  • http://www.phrack.org/phrack/56/p56-0x05 - BYPASSING STACKGUARD AND STACKSHIELD, by Bulba and Kil3r (hert.org)
  • http://www.ngssoftware.com/papers/defeating-w2k3-stack-protection.pdf - Windows 2003 Server - Defeating the stack protection mechanism, by David Litchfield (NGSSoftware Ltd)
  • http://www.securityfocus.com/infocus/1846 - A new way to bypass Windows heap protections, by Nicolas Falliere
  • http://www.phrack.org/show.php?pb&a=5 - Bypassing 3rd Party Windows Buffer Overflow Protection, by anonymous, Jamie Butler and anonymous
или введите имя

CAPTCHA