Это не статья в полном смысле этого слова, скорее это заметки по безопасному программированию на языке C. Моя цель - это рассмотрение наиболее популярных ошибок, которые можно встретить в приложениях на данном языке. Сразу скажу, что я не ставил перед собой задачу описать эксплутацию этих уязвимостей, так как это совсем другая тема. Прочитав этот материал, вы сможете анализировать свои программы на предмет описанных мной багов. Позже, вы научитесь самостоятельно определять уязвимые места в своем коде, даже больше того, у вас появится возможность дальше развиваться в направлении анализа кода. При написании я рассчитывал на средний уровень читающего, я старался писать доступным языком, но совсем банальные вещи, которые по моему мнению должен знать читатель, я не определял. Содержание: 1) Переполнение буфера 2) Форматные строки 3) Уязвимость единичного смещения и некорректное завершение строк продолжение следует... 1) Переполнение буфера Начнем с самого основного, уязвимости, связанные с переполнением буфера. Буфер может переполняться в самых разных местах памяти, включая стек и кучу. Переполнение буфера обычно происходит, когда программа пытается записать данные за пределы конца буфера. Множество стандартных функций из crt не имеют никакого представления о размерах приемных буферов (strcpy, strcat, gets и тд). Несмотря на то, что переполнения можно избежать обычной проверкой перед вызовом функции, такие ошибки всеравно допускаются, хотя в чистом виде их можно встретить либо в совсем старых исходниках, либо в работах начинающих программистов. Отдельно стоит упомянуть и про то, что стандартные строковые функции имеют аналоги, которые позволяют ограничить размер записываемых данных. Таким образом, вместо strcpy, strcmp, и sprintf используются strncpy, strncmp, snprint, соответственно. Простой пример уязвимой программы: Code: #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buffer[16]; if (argc < 2) { printf("Usage: %s arg", argv[0]); return 1; } else strcpy(buffer, argv[1]); return 0; } Переполнение происходит из-за того, что функция strcpy не имеет понятия о размере буфера, таким образом, если argv[1] превысит размер buffer, то данные выйдут за границу приемного буфера и появиться возможность эксплутации программы. Теперь рассмотрим безопасный вариант: Code: #include <stdio.h> #include <string.h> #define SIZE 10 int main(int argc, char *argv[]) { char buffer[SIZE]; if (argc < 2) { printf("Usage: %s arg", argv[0]); return 1; } else { strncpy(buffer, argv[1], SIZE - 1); buffer[SIZE - 1] = '\0'; } return 0; } Здесь используется безопасный аналог функции strcpy, который четко задает размер копируемых данных и строка в конце завершается нулем, подробнее про нуль-сивол читайте ниже. Тема довольно широко описана в большом количестве статей, поэтому я не стал уделять много внимания описанию данного вида уязвимостей. Стоит лишь упомянуть и про то, что производители операционных систем не стоят на месте и пытаюстя внедрить новые технологии по борьбе с данным классом уязвимостей. В windows доступны некоторые программные решения, которые предотвращают выполнение кода за пределами переполненного буфера, если такое переполнение было осуществлено. Среди этих решений - DEP в Windows XP SP2 и выше, который кстати существует и на аппаратном уровне. ОС такого вида называют системами с неисполняемым стеком. Думаю ни для кого не секрет, что все что создал человек можно взломать, DEP и прочие защиты не исключение. Советую ознакомиться с некоторыми работами по теме переполнения: http://forum.antichat.ru/thread26791.html http://shellcode.ru/index.php?name=News&file=article&sid=11 2) Форматные строки Не смотря на то, что уязвимости форматных строк устаревают, я всеравно посчитал нужным рассказать об этом, так как и сейчас можно встретить уязвимые приложения, которые встречаются в основном в UNIX. Эта уязвимость базируется на том, что атакующий может контролировать форматную строку. Под форматной строкой понимается форматная строка которая передается функциям, получающим аргументы в стиле printf. Стоит атакующему получить контроль над форматной строкой и он получит возможность передать функцие спецификаторы, приводящие к самым разным результатам. Рассмотрим небольшой пример: Code: #include <stdio.h> int main(int argc, char *argv[]) { char buff[16], buff_[16]; printf("What is your name? "); gets(buff); sprintf(buff_ , "Hello, %s", buff); printf(buff_); return 0; } Теперь рассмотрим спецификаторы. Начнем с краткой информации по всем типам, которую я позаимствовал с википедии. Наиболее интересны для нас лишь несколько спецификаторов. Спецификатор %s базируется как указатель на строку, тоесть интерпретатор форматного вывода извлекает из стека парный ему аргумент, считая что это указатель на строку, понятно, что там может лежать вовсе не нужная нам строка. Отдельно упомяну нуль в конце, так как язык С использует ASCIIZ строки, то по стандарту строки должны закнчиваться нулем в конце. Вывод "мусорной" строки может привести к самым разным последствиям, в том числе к выводу произвольного дампа памяти и разрушению стека. Такой тип уязвимости называют атакой на строку форматирования. Откомпилируйте пример и введите заместо верного ответа спецификатор %s, обратите внимание на результат. Продолжим рассматривать спецификаторы, спецификатор %x выводит парное двойное слово из стека в шестнадцатиричном формате, это может пригодиться, если вам нужно получить адреса определенных данных. Также нужно сказать про спецификатор %n, цель которого записать в парный указатель количество выведенных байтов на данный момент. Перезаписываться будет не сам указатель, а область памяти, на которую он указывает. Это открывает огромные возможности нападающему. Ошибки форматной строки достаточно легко обнуружать в ходе анализа исходного кода, поэтому данный тип уязвимостей с каждым днем теряет свою актуальность. 3) Уязвимость единичного смещения Уязвимость единичного смещения очень актуальна на данный момент, смсысл данной уязвимости состоит в том, что небольшое число байт записывается за пределы выделенной памяти. Чаще всего, именно один байт, в результате некорректного завершения строки нулем. Посмотрим на вызов функции strncat: Code: strncat(buf, tmp, sizeof(buf) - strlen(buf)); Ошибка единичного смещения в данном примере возникает из-за того, что strncat завершает выходную строку нулем, тоесть если третий аргумент данной функции не будет равен объему оставшегося места в выходном буфере с вычетом одного байта, то \0 запишется за пределами нашего буффера. Значит безопасный вызов функции будет таким: Code: strncat(buf, tmp, sizeof(buf) - strlen(buf) - 1); Не стоит проверять на уязвимость только функции для работы со строками, обращайте внимание на все функции работы с памятью. Хотя эксплутировать данную уязвимость очень сложно, вероятность есть всегда. Далее рассмотрим ошибки некорректного заверешния строк в C, как известно ASCIIZ строки завершаются нулем, что не очень практично. Однако, от этого никуда не деться, поэтому разберемся с этим поподробнее. Допустим строка не заканчивается нулем, что тогда? Тогда все дальнейшее содержимое памяти будет рассматриваться как строка, до первого нуль-символа естественно. Последствия могут быть самыми разными, от включения в строку "мусора" до аварийного заверешния программы. Основная проблема кроется в тех самых функциях рантайма для работы со строками. Скажем strncpy не завершит строку нулем, если место в приемном буфере кончилось, поэтому нужно явно дописывать нуль-символ в конец строки. Нельзя недооценивать данную уязвимость, так как простор для действий атакующего ограничивается лишь рядом обстоятельств в виде рядом лежащих данных. Также обращаю ваше внимание на пропуск завершающего нуль-символа, скажем, если после пропуска завершителя произойдет запись данных - это может привести к фатальным последствиям, вплоть до выполнения произвольного кода. Первые 3 пункта я написал, жду конструктивной критики и исправления логических и грамматических ошибок, так как писал не совсем в адекватном состоянии. Так как всю тему целиком охватить достаточно сложно, то я буду пополнять заметки со временем. Постараюсь ответить на любые нормальные вопросы по теме.
боян но про new\delete\malloc\realloc\free\ хотелось бы услышать....ну или ссылку на статью где изложены все подробности...
Думаю это теоретическая статья. Было бы очень круто увидеть приведенные примеры программ и так сказать експлойты юзающие данные баги в защите. Это было бы очень наглядным к сказанному. Ni0x Спс за статью!
inv, ну я же говорю, не надо писать мне боян и прочие выражения такого рода. Что именно ты хочешь услышать? Есть множество уязвимостей, основанных на функциях, связанных с выделением/освобождением памяти. GlOFF, этот материал изначально позиционировался как теоритический, я специально не стал приводить примеры эксплутации данных уязвимостей, так как по каждому пункут есть свои нюансы и свои техники, рассматривать каждый аспект по отдельности с эксплутацией подобно написанию книги. У меня в планах есть написание статьи, где будет и практика и теория, но ее реализация будет после того, как я закончу с этими заметками.
да все подобные статьи не актуальны и по сути уже не нужны. интересно было бы почитать про анализ существующего кода на ошибки, возможные в будущем ошибки, расчет архитектуры программы... естественно я не про анализ 5-строчечного кода говорю. зы хорошую статью греат писал насчет вылова ошибок доступа к участкам памяти
ZaCo, зря ты так, на данный момент есть необкатанные типы уязвимостей, про которые ты наврятле найдешь нормальную информацию. А про анализ - это да, это я согласен. Думаю, решим.
Ой бред какойто. Аргумент извлекается в любом случае и НЕ БЫТЬ его там не может - это стек, там всегда есть данные (ну почти.. границу стека в данном случае не рассматриванием). Просто я к тому, что ничего перебирать он не будет - это бред. А уж будет ли извлеченный дворд указателем - проблемы программиста Приведенный пример неуязвим же на этот тип атаки. Буфер не передается как форматная строка в данном случае. Стоит переписать так: Code: #include <stdio.h> int main(int argc, char *argv[]) { char buff[16]; printf("What is your name? "); gets(buff); printf("Hello, "); printf(buff); return 0; } Тогда действительно будут проблемы при вводе форматных символов. Я видел книжку с отличным описанием данного типа атак. К сожалению, не помню ни названия, ни автора - читал у Cr4sh'а дома когда бухали по пьяни)) Чтото про эксплоиты
IMHO, вместо замены strcpy на strncpy, лучше определять длину строки и если длина превышает размер буфера просто вывести ошибку.
>>Тогда действительно будут проблемы при вводе форматных символов. >>Я видел книжку с отличным описанием данного типа атак. К сожалению, не помню >>ни названия, ни автора - читал у Cr4sh'а дома когда бухали по пьяни)) >>Чтото про эксплоиты меньше надо пить) Эрикссон. "Исскуство экплойта" или "The Art Of Exploitation"
Прочитал статью - написал такую же. Твою статью прочитал Вася - написал такую же. Маша прочитала статью Васи - написала подобную. Так развивается кодинг на античате.
Немножко дополнительной информации о практических примерах: - Классная, но довольно старая для применения сейчас, статья xCrZx "Эксплойтинг Win32". - Доки на milw0rm.com/papers - Еще я как то переводил для первого номера HC.Ezine один из паперов про эксплуатирование формат стринг баги. >>"Исскуство экплойта" или "The Art Of Exploitation" <<"Искусство эксплуатирования" учим русский и английский.
Английский стоит учить переводчикам книги, ибо называется она именно так. В принципе, дословный перевод никто не просит - переводят ведь смысл, а не слова, не так ли?
Да по-моему (по тому как я написал) - даже звучит лучше, и вообще, толковее. зы Искусство сплоита))) мухахаха долбить 1 приложение механически - искусство?)) написать чтоли издателям дибилам?