Ассемблирование без секретов

             

ассемблерный фрагмент


Попытка загнать текст программы в ассемблерную вставку ни к чему хорошему не приводит. Компилятор кроет нас матом, но не компилит. Редиска! Приходится действовать стратегически. То есть свирепо и радикально. Убираем директивы .code и .data вместе с ненужной инструкций "ret" (в оригинале она завершает программу, пользуясь тем фактом, что при запуске PE-файла на вершине стека лежит адрес на термирующую процедуру, однако, в стековой фрейме нашей ассемблерной вставки ничего подобного нет!).

Метку start можно, в принципе, и не убирать, но на фиг она будет торчать?! А вот к метке s0 подобный диалектический подход уже не приемлем и с ней надо кончать. В смысле — избавляться от этой твари любой ценой. Ну не поддерживает встроенный ассемблер директивы "db", что тут поделать?! Приходится объявлять строковую константу средствами самого Си. Она может быть размещена как в стеке (т. е. объявлена как локальная переменная), так и в секции данных (т. е. объявлена как глобальная переменная). В действительности, строки всегда размещаются в секции данных, а стек заносится лишь их копия, а копия — это оверхид, то есть накладные расходы и прочий маст-дай (примечание: некоторые оптимизирующие компиляторы, в том числе и ms vc, могут "загонять" строковые переменные в стек при помощи инструкций PUSH XXYYZZ, и тогда в секции данных их уже не оказывается).

Если переменная объявлена как глобальная, то ключевое слово offset сохраняет свою силу и компилятор не матерится. С локальными переменными все сложнее. Конструкция "push offset s0" в этом случае разворачивается компилятором в "push offset [ebp+x]", что с точки зрения синтаксиса совершенно бессмысленно! Но убирать "offset" нельзя, поскольку "push [ebp+x]" заталкивает в стек отнюдь не указатель на s0, а… значения первых четырех байт, то есть ведет себя как "*((DWORD*)s0)". Правильный вариант выглядит так: "lea eax,s0/push eax" (разумеется, вместо eax можно использовтаь любой другой регистр общего назначения).

Еще один нюанс — конструкция "call MessageBoxA" выполняется совсем не так, как задумывалось, поскольку вместо MessageBoxA коварный компилятор подставляет отнюдь не адрес самой MessageBoxA, а указатель на двойное слово, хранящее адрес MessageBoxA! Следовательно, чтобы программа не развалилась и не умерла, необходимо использовать префикс ds и тогда вызывающий код будет выглядеть так: "call ds:MessageBoxA"

Обобщив все вышесказанное, мы получаем следующую программу. Даже две! С объявлением строки как глобальной и локальной переменной:

#include <windows.h>

// глобальная переменная выводимая на экран

char s0[]="hello,wordl\n";

main()

{

__asm

{

       push 0

       push 0

       push offset s0

       push 0

       call ds:MessageBoxA  ; добавляем

ds:

       }

}



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