Устанавливаем и удаляем thunk
Остается заточить несложную процедуру, устанавливающую jump на thunk и восстанавливающую содержимое API-функции перед ее вызовом. Эта функция называется memcpy. Ну… почти memcpy. Чтобы установка jump'а завершилась успехом необходимо вызвать VirtualProtect с флагом PAGE_READWRITE, что слегка усложняет реализацию, однако, не столь радикально, чтобы впадать в депрессию. Это можно запрограммировать как на ассемблере, так и на Си. На Ассамблее — круче, на Си — быстрее.
Ниже приводится ассемблерный листинг с несколькими интересными хаками:
// устанавливает/удаляет thunk
//============================================================================
__declspec( naked ) _do_asm(char *src)
{
__asm
{
; сохраняем регистры, которые будут изменены
push ecx
push esi
push edi
; резервируем место под локальные переменные
push esp ; резервируем место под old-old (hack!!!)
push eax ; резервируем место под old
; вызываем VirtualProtect(p,0x1000,PAGE_READWRITE, &old);
; присваивая себе атрибут записи
push esp ; &old
push PAGE_READWRITE ; нельзя
PAGE_EXECUTE_READWRITE!
push
0x1000 ; size
push
[p] ; указатель на регион
call ds:VirtualProtect
; копируем память из src
в p
двойными словами
mov ecx, JUMP_SZ/4 ; size в дв. словах
mov esi, [esp+18h] ; src !!!следить за смещением!!!
mov edi, [p] ; dst
rep movsd ; копируем!
; вызываем VirtualProtect(p,0x1000,old,&old-old)
; восстанавливая прежние атрибуты зашиты
push esp ; old (hack!!!)
push
1000h ; size
push
[p] ; указатель на регион
call ds:VirtualProtect
pop eax ; выталкиваем old
;pop eax ; old-old уже вытолкнут v_prot
; восстанавливаем измененные регистры
pop edi
pop esi
pop ecx
; выходим
retn
}
}
Листинг 6 ассемблерный код процедуры, устанавливающей и снимающий thunk с API
Хак номер один. Резервирование места под локальные переменные командой push. Ну это и не хак вовсе. Так даже компиляторы поступают! Инструкция push eax забрасывает на верхушку стека содержимое регистра eax, а команда push esp заталкивает указатель на eax, передавая его как аргумент функции VirtualProtect, которая записывает сюда текущие атрибуты, выталкивая указатель из стека по завершении. А это значит, что на вершине стека вновь оказывается локальная переменная, с прежними атриумами. Вот только передать ее функции VirtualProtect через push esp уже не получится, поскольку она ожидается во втором слева аргументе. Компилятор (даже самый оптимизирующий) наверняка влепил бы сюда команды типа push eax/push esp/push [esp+8], что слишком длинно и вообще маст дай.
Вот если бы в стеке уже содержался указатель на фиктивную ячейку памяти, которую было можно передать VirtualProtect, но, к сожалению, его там нет… Но! Ведь его можно очень легко сделать! Для этого достаточно лишь передать аргумент до первого вызова VirtualProtect, что и делаем команда push esp с комментарием "hack!!!". Да! Аргумент для второго вызова VirtualProtect заносится в стек в первую очередь, экономя целых три машинных команды. Почему три? Да потому, что одну из двух локальных переменных выталкивает сама функция VirtualProtect и это второй хак!
Вот оно — отличие между ассемблером и языками высокого уровня. На ассемблере мы можем писать намного более эффективно и компактно, пускай даже ценою потерянного времени, но зато как интересно оптимизировать программы, выкидывая из них все ненужное!