SEH на службе у контрреволюции Крис Касперски aka мыщъх Спецвыпуск Xakep, номер #045, стр. 045-048-3 Следующим в цепочке идет фрейм, сформированный стартовым кодом. Он расположен намного ниже: от вершины стека его отделяют аж 5Сh байт – и это в демонстрационной программе, содержащей минимум переменных! Первичный фрейм, назначаемый операционной системой, отстоит от вершины стека на целых 8Сh байт, а в реальных полновесных приложениях и того больше (идентифицировать первичный фрейм можно по "ненормальному" адресу SEH-обработчика, лежащего в старших адресах первой половины адресного пространства). Его линейный адрес, равный 12FFE0h, идентичен для первого потока всех процессов, запущенных в данной версии операционной системы, что создает благоприятные условия для его подмены. Однако для гарантированного перехвата управления shell-код должен перехватывать текущий, а не первичный обработчик, поскольку до первичного обработчика исключение может и не дожить. Проверь, если при переполнении буфера бессмысленной строкой наподобие "XXXXX" возникает стандартное диалоговое окно критической ошибки, подменять первичный обработчик можно, в противном случае его перезапись ничего не даст и shell-код сдохнет прежде, чем успеет получить управление. Первичный фрейм всех последующих потоков располагается на dwStackSize байт выше предыдущего фрейма, где dwStackSize – размер памяти, выделенной потоку (по умолчанию 4 Мбайт на первый поток и по 1 Мбайт на все последующие). Доработаем нашу тестовую программу, включив в нее следующую строку: CreateThread(0, 0, (void*) main, 0,0, &xESP); gets(&xESP); Результат ее прогона будет выглядеть приблизительно так: Заметно, что первичный SEH-фрейм всех потоков находится на идентичном расстоянии от текущей вершины стека, что существенно облегчает задачу его подмены. Первичные фреймы первого и второго потоков разнесены на 4 Мбайт (51FFDCh – 12FFE0h == 0x3EFFFC ~4 Мбайт), а остальные – на 1 Мбайт (61FFDCh – 51FFDCh == 71FFDCh – 61FFDCh == 10.00.00 == 1 Мбайт), в общем, разобраться можно. Поскольку большинство серверных приложений конструируется по многопоточной схеме, уметь ориентироваться в потоках жизненно необходимо, иначе вместо перехвата управления атакующий получит полный DoS. Перехват управления Существует, по меньшей мере, два пути перехвата управления. Путь первый: проанализировать уязвимую программу и определить, какой из обработчиков будет текущим на момент переполнения и где именно расположен его SEH-фрейм (учитывая, что адрес последнего может быть непостоянным и зависящим от множества трудно прогнозируемых обстоятельств, например, от рода и характера запросов, предшествующих переполнению). Теперь придумайте, как переполнить буфер так, чтобы затереть handler, подменив содержащийся в нем указатель на адрес shell-кода. Значение поля prev не играет никакой роли (shell-код ведь не собирается просто так возвращать таким трудом захваченное управление). |