Ассемблерные извращения - натягиваем стек


Защита адреса возврата от переполнения


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

Ассемблер предоставляет по меньшей мере два надежных механизма, до которых еще не компиляторы "додумались". Первое и самое простое— это _два_ стека: один для хранения адресов возврата, другой: для передачи аргументов и локальных переменных. Кстати говоря, существуют процессорные архитектуры, в которых этот механизм реализован изначально, но x86 семейство к ним увы не относятся, поэтому приходится брать в лапы напильник и точить. Или торчать? Неееет, торчать мы будем потом, когда забьем косяк, а пока лучше поточим.

Собственно говоря, для организации двух раздельных стеков нам требуется всего лишь один дополнительный регистр (который можно выделить из пула регистров общего назначения). Пусть это будет регистр EBP, указывающий на стек с локальными переменными. Собственно говоря, неправильно будет называть его стеком, поскольку в операционных системах семейства Windows стек представляет собой _особый_ регион памяти, подпираемый сверху сторожевой страницей page-guard. Мы же разместим свой стек в памяти, выделенной функцией VirtualAlloc или (если хочется оптимизации) в .BSS сеции PE-файла, выделение которой обходится очень дешевого (в плане машинного времени). Но это все детали реализации. Будем считать, что ESP указывает на нормальный стек, а EBP — на "рукотворный". Как тогда будет происходить вызов функций и передача аргументов?

А вот так:

; // подготовительные операции

MOV EBP, [XXX]       ; XXX

- указатель на "рукотворный" стек

MOV ESP, ESP  ; ;-)

; // передача аргументов функции

MOV

[EBP+00h], arg_a

MOV [EBP+04h], arg_b



MOV [EBP+08h], arg_c

// вызов самой функции

CALL func

// ================================================================================

; // реализация самой функции

func:

ADD EBP, local_var_size           ; резервируем память под локальные переменные

MOV ECX, [EBP-local_var_size+04h] ; загрузка аргумента arg_b

в регистр ECX

MOV ESI, [EBP-local_var_size+08h] ; загрузка

аргумента

arg_c в регистр ESI

MOV EDI, EBP                      ; грузим в EDI

указатель конец области лок. пер.

SUB EDI, local_var_size           ; вычисляем указатель на локальный буфер

                                  ; (в данном случае он расположен по смещению 00h

                                  ;  относительно фрейма)

REP MOVSB                         ; копируем arg_b байт из arg_c

в лок. буффер

; // делаем еще что-то полезное

RET                               ; выходим из функции



Содержание раздела