Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

SEH на службе у контрреволюции

Крис Касперски aka мыщъх

Спецвыпуск Xakep, номер #045, стр. 045-048-2


При создании нового процесса операционная система автоматически добавляет к нему первичный SEH-фрейм с обработчиком по умолчанию, лежащий практически на самом дне стековой памяти, выделенной процессу. "Дотянуться" до него последовательным переполнении практически нереально, так как для этого потребуется пересечь весь стек целиком! Таких катастрофических переполнений старожилы не встречали уже лет сто!

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

Разработчик может назначать и свои обработчики, автоматически создающиеся при упоминании волшебных слов try и except (такие обработчики мы будем называть пользовательскими). Несмотря на все усилия Microsoft основная масса программистов совершенно равнодушна к структурной обработке исключений (некоторые из них даже такого слова не слышали!), поэтому вероятность встретить в уязвимой программе пользовательский SEH-фрейм достаточно невелика, но все же она есть В противном случае для подмены SEH-обработчика (а первичный SEH-обработчик в нашем распоряжении есть всегда) придется прибегнуть к индексному переполнению или псевдофункции poke, о которой уже шла речь в других статьях номера.

Для исследования структурных обработчиков исключений напишем нехитрую программку, трассирующую SEH-фреймы и выводящую их содержимое на экран. Законченная реализация может выглядеть, например, так:

Листинг

Простой визуализатор SEH-фреймов

main(int argc, char **argv)

{

int *a, xESP;

__try{

__asm{

mov eax,fs:[0];

mov a,eax

mov xESP, esp

} printf("ESP: %08Xh\n",xESP);

while((int)a != -1)

{

printf("EXCEPTION_REGISTRATION.prev: %08Xh\n"\

"EXCEPTION_REGISTRATION.handler: %08Xh\n\n", a, *(a+1));

a = (int*) *a;

}

}

__except (1 /*EXCEPTION_EXECUTE_HANDLER */) {

printf("exception\x7\n");

}

return 0;

}

Откомпилировав программу и запустив ее на выполнение, мы получим следующий результат (естественно, адреса SEH-фреймов и обработчиков в твоем случае, скорее всего, будут другими):

Листинг

Раскладка SEH-фреймов в памяти

ESP : 0012FF54h ; текущий указатель вершины стека

EXCEPTION_REGISTRATION.prev : 0012FF70h ; "пользовательский" SEH-фрейм

EXCEPTION_REGISTRATION.handler : 004011C0h ; "пользовательский" SEH-обработчик

EXCEPTION_REGISTRATION.prev : 0012FFB0h ; SEH-фрейм стартового кода

EXCEPTION_REGISTRATION.handler : 004011C0h ; SEH-обработчик стартового кода

EXCEPTION_REGISTRATION.prev : 0012FFE0h ; первичный SEH-фрейм

EXCEPTION_REGISTRATION.handler : 77EA1856h ; SEH-обработчик по умолчанию

Пользовательский SEH-фрейм, сформированный ключевым словом try, лежит в непосредственной близости от вершины стека текущей функции, и его отделяют всего 1Сh байт (естественно, конкретное значение зависит от размера памяти, выделенной под локальные переменные, ну и еще кое от чего).

Назад на стр. 045-048-1  Содержание  Вперед на стр. 045-048-3