Ломаем структуры Мысла Владислав Спецвыпуск Xakep, номер #045, стр. 045-036-1 aka DigitalScream (digitalscream@real.xakep.ru) Структура не всегда критерий целостности Явно происходило переполнение, но что перезаписывалось и как это влияло на дальнейшее исполнение программы, было загадкой. Что-то смутно напоминало ситуацию с выделением памяти в куче через функцию malloc()… Ошибки переполнения буфера часто требуют от хакера проявить смекалку. Особенно это касается тех моментов, когда появляется необходимость в подделке каких-либо данных. Если ты сталкивался с переполнениями в куче, то понимаешь, о чем идет речь. В данной статье я приведу несколько примеров того, как эксплуатируются уязвимости, требующие фальсификации структурированных участков памяти, и как этим может воспользоваться злоумышленник для исполнения своего кода. Поехали! Все данные в памяти представлены одинаково. Не имеет значения, с чем работает программа: со строками, числами, структурами и т.д. Так или иначе, все данные – это набор байт определенной длины, и ничего более. Все ограничения в обработке созданы программистом, и они существуют только на уровне понимания кода. Если ты создаешь строку, то она в памяти ничем не отличается от массива чисел или координат какого-либо многоугольника. Другое дело – интерпретация данных. Если это строка, то ты знаешь, что она должна заканчиваться нулем, если это координаты, то их количество должно быть парным и т.д. Переполнение структур При переполнении буфера хакер имеет дело, в основном, со строками, и единственное ограничение, которое накладывается на shell-код, – это отсутствие в нем нулевых байтов. Но бывают ситуации, когда переполнение происходит в обработке строки, которая является частью структуры. Давай взглянем на пример 1. Листинг Пример 1. Программа проверки имени и пароля struct credentials { char name[0x10]; char pass[0x10]; bool admin; }; void CheckUser(credentials* cUser) { if(!strcmp(cUser-<name,"adm") && !strcmp(cUser-<pass,"adm") ) cUser-<admin = true; } int _tmain(int argc, _TCHAR* argv[]) { credentials* cUser = new credentials; cUser-<admin = false; char* name = argv[1]; char* pass = argv[2]; memcpy( cUser-<name, name, strlen(name) ); memcpy( cUser-<pass, pass, strlen(pass) ); cUser-<name[strlen(name)] = '\0'; cUser-<pass[strlen(pass)] = '\0'; CheckUser(cUser); ...... return 0; } Это программа, которая проверяет имя пользователя и пароль. Если они совпадают и равны «adm» – пользователь считается администратором, если не совпвдают – гостем. Сразу можно заметить, что для заполнения структуры credentials используется команда memcpy без проверки на длину передаваемых данных. Этим можно воспользоваться, чтобы перезаписать память приложения и, возможно, получить управление над ним. Чтобы подтвердить теорию, нужно поиграть с передаваемыми приложению данными. Если запустить программу с параметрами «demo demo», она скажет, что ты являешься гостем, «adm adm» – вернет, что ты админ. Кажется, все в порядке, но не стоит забывать о переполнении! Передай приложению строку «hacker aaaaaaaaaaaaaaaa\x01», и она опознает тебя как администратора. Причина такого поведения ясна – передавая длинный пароль, ты перезаписываешь значение переменной admin, и именно она хранит в себе твой статус в системе. Если переполнения не происходит, переменная admin равна нулю, но поскольку ты передал пароль из 16 символов «а» и одного байта «0x01», а размер переменной pass – всего 16, то «0x01» перезапишет значение admin и система сочтет тебя администратором. |