Страница с сайта Владислава Пирогова Ассемблер и не только.


Ассемблерный код в стеке (пример маленького исследования).



Один из способов модификации исполняемого кода во время исполнения это использовать стек. Часть кода можно перенести в стек и выполнить там. Разумеется при переносе код можно модифицировать. Наконец ассемблерные команды можно хранить и в сегменте данных, а затем перенести в стек и там выполнить. Использовать стек в языке высокого уровня хоть и можно, но с целым рядом оговорок. А на ассемблере это можно сделать без особых проблем. Впрочем, кое-какие проблемы появится могут. Вот об этом моя маленькая статья.
И так имеем следующую консольную программу.

.586P
.MODEL FLAT,STDCALL
includelib f:\masm32\lib\user32.lib
EXTERN MessageBoxA@16:NEAR
;------------------------------------------------
_DATA SEGMENT
TEXT1 DB 'Я в стеке!',0
TEXT2 DB 'Сообщение из стека',0
_DATA ENDS
_TEXT SEGMENT
START:
;вызвать процедуру
CALL PROC1
RETN
PROC1 PROC
PUSH 0
PUSH OFFSET TEXT2
PUSH OFFSET TEXT1
PUSH 0
CALL MessageBoxA@16
RETN
PROC1 ENDP
_TEXT ENDS
END START

Пусть программа называется prog1.asm. Откомпилируем ее.
ML /c /coff prog1
LINK /SUBSYSTEM:CONSOLE prog1.obj
В результате появится исполняемый модуль prog1.exe, в результате выполнения которого на экране появится MessageBox с соответственным сообщением.
Попробуем теперь решить проблему в лоб. Скопируем содержимое процедуры PROC1 в стек и запустим ее там. Вот эта программа.

.586P
.MODEL FLAT,STDCALL
includelib f:\masm32\lib\user32.lib
EXTERN MessageBoxA@16:NEAR
;------------------------------------------------
_DATA SEGMENT
TEXT1 DB 'Я в стеке!',0
TEXT2 DB 'Сообщение из стека',0
_DATA ENDS
_TEXT SEGMENT
START:
;подготовить стек
MOV EBP,ESP
MOV ECX,OFFSET L1
SUB ECX,PROC1
;выделяем место в стеке
SUB ESP,ECX
;скопировать код
MOV EDI,ESP
LEA ESI,PROC1
CLD
REP MOVSB
;вызвать процедуру из стека
CALL ESP
;восстановить стек
MOV ESP,EBP
RETN
PROC1 PROC
PUSH 0
PUSH OFFSET TEXT2
PUSH OFFSET TEXT1
PUSH 0
CALL MessageBoxA@16
RETN
PROC1 ENDP
_TEXT ENDS
END START

Однако нас ждет разочарование. После запуска программы появляется сообщение ОС об ошибке. Попробуем разобраться в чем здесь дело обратившись к отладчику OLLYDBG. Запустив программу под отладчиком, выполним ее в пошаговом режиме. Дойдя до команды CALL ESP нажмем F7 и окажемся в том месте стека, куда была скопирована процедура. На первый взгляд код скопировался корректно (см. фрагмент ниже). Однако что это?

000CFFB0 6A 00 PUSH 0
000CFFB2 68 0B304000 PUSH 40300B
000CFFB7 68 00304000 PUSH 403000
000CFFBC 6A 00 PUSH 0
000CFFBE E8 02000000 CALL 000CFFC5
000CFFC3 C3 RETN

Адрес, по которому осуществляется вызов процедуры, находится здесь же в стеке. Откуда здесь взяться какому либо переходу на MessageBox? Все очень просто. В команде CALL MessageBoxA@16 транслятор ассемблера подставляет относительные адреса. Вот оно что! Что же делать? Неужели придется корректировать адрес при переносе в стек? К счастью процедуру можно вызвать и так: LEA EBX, MessageBoxA@16/CALL EBX. Попробуем проверить. Перепишем программу.

.586P
.MODEL FLAT,STDCALL
includelib f:\masm32\lib\user32.lib
EXTERN MessageBoxA@16:NEAR
;------------------------------------------------
_DATA SEGMENT
TEXT1 DB 'Я в стеке!',0
TEXT2 DB 'Сообщение из стека',0
_DATA ENDS
_TEXT SEGMENT
START:
;подготовить стек
MOV EBP,ESP
MOV ECX,OFFSET L1
SUB ECX,PROC1
;выделяем место в стеке
SUB ESP,ECX
;скопировать код
MOV EDI,ESP
LEA ESI,PROC1
CLD
REP MOVSB
;вызвать процедуру из стека
CALL ESP
;восстановить стек
MOV ESP,EBP
RETN
PROC1 PROC
PUSH 0
PUSH OFFSET TEXT2
PUSH OFFSET TEXT1
PUSH 0
LEA EBX,MessageBoxA@16
CALL EBX
RETN
PROC1 ENDP
L1:
_TEXT ENDS
END START

После трансляции запустим программу. На этот раз ошибки не появилось, но окна MessageBox вышло какое-то кривое. Точнее без всяких надписей. Попытка выполнить программу под отладчиком ни к чему не приводит. Точнее мы убеждаемся, что на этот раз вызов MessageBox будет производится по правильному адресу. В чем же здесь дело? проведем следующий эксперимент. Заменим в программе команду CALL ESP на CALL PROC1, т.е. проверим, а будет ли выполняться сама процедура? И, о удивление? Результат получается аналогичный. Что же вызвало ошибку? Поскольку раньше процедура выполнялась нормально, попробуем убирать по одной команде, которые мы добавили для копирования процедуры в стек и выясним, как команда приводит в конечном итоге к ошибке. Оказывается такой командой является SUB ESP,ECX. Ну тут уж подозрение начинает закрадываться к нам в душу. Что же в этой команде плохого. Сплошь и рядом такие команды используют и ассемблерщики и компиляторы. Значение, которое хранится в ECX не велико, чтобы выйти за границы стека, да и ошибка в этом случае была бы другой. Наконец доходит: адрес в стеке должен быть кратен 4. У нас, очевидно, это условие не выполняется. Попробуем откорректировать содержимое ECX, прежде чем вычитать его из ESP. Это можно сделать разными способами. Например так: SHL ECX,2, т.е. умножит содержимое на четыре. А можно так (если четыре для вас слишком большое число): AND ECX,11111111111111111111111111111100B/SHL ECX,1. В обоих случаях результат будет положительным, т.е. код в стеке заработает верно. Но проще использовать директиву ALIGN 4, так чтобы адреса PROC1 и L1 оказались выровнены по двойному слову. Вот окончательный вариант программы.

.586P
.MODEL FLAT,STDCALL
includelib f:\masm32\lib\user32.lib
EXTERN MessageBoxA@16:NEAR
;------------------------------------------------
_DATA SEGMENT
TEXT1 DB 'Я в стеке!',0
TEXT2 DB 'Сообщение из стека',0
_DATA ENDS
_TEXT SEGMENT
START:
;подготовить стек
MOV EBP,ESP
MOV ECX,OFFSET L1
SUB ECX,PROC1
;выделяем место в стеке
SUB ESP,ECX
;скопировать код
MOV EDI,ESP
LEA ESI,PROC1
CLD
REP MOVSB
;вызвать процедуру из стека
CALL ESP
;восстановить стек
MOV ESP,EBP
RETN
ALIGN 4
PROC1 PROC
PUSH 0
PUSH OFFSET TEXT2
PUSH OFFSET TEXT1
PUSH 0
LEA EBX,MessageBoxA@16
CALL EBX
RETN
PROC1 ENDP
ALIGN 4
L1:
_TEXT ENDS
END START

Пирогов Владислав