Следующие фрагменты кода были проверены с помощью mingw-g ++, но должны работать в VC ++ с небольшими изменениями. Полные источники доступны на Launchpad: 1
Единственный способ безопасно сохранить данные, относящиеся к вызову, - это сохранить их в стеке. Одним из способов является вращение части стека.
отрывок patch.s (patchfun-rollstack):
sub esp, 4 # allocate scratch space
mov eax, DWORD PTR [esp+4] # first we move down
mov DWORD PTR [esp], eax # our return pointer
mov eax, DWORD PTR [esp+8] # then our parameters
mov DWORD PTR [esp+4], eax
mov eax, DWORD PTR [esp+12]
mov DWORD PTR [esp+8], eax
mov eax, DWORD PTR [esp+16]
mov DWORD PTR [esp+12], eax
mov eax, DWORD PTR [esp+20]
mov DWORD PTR [esp+16], eax
mov eax, DWORD PTR [esp] # save return pointer
mov DWORD PTR [esp+20], eax # behind arguments
add esp, 4 # free scratch space
call __Z8receiveriiii
mov eax, DWORD PTR [esp+16] # restore return pointer
mov DWORD PTR [esp], eax
ret
Мы опустили ebp
здесь. Если мы добавим это, нам нужно будет использовать 8 байтов пустого пространства и сохранить и восстановить ebp
, а также eip
. Обратите внимание, что когда мы восстанавливаем указатель возврата, мы перезаписываем параметр a
. Чтобы избежать этого, нам нужно снова повернуть стек обратно.
Другой способ заключается в том, чтобы вызываемый пользователь знал о дополнительных данных в стеке и игнорировал их.
patch.s (patchfun-ignorepointers):
push ebp
mov ebp, esp
call receiver
leave
ret
receiver.cc:
void receiver(const void *epb, const void *eip, int a,int b,int c,int d)
{
printf("Arguments %d %d %d %d\n",a,b,c,d);
}
Здесь я включил epb, если вы удалите его из asm, все, что останется, это call
и ret
, а получателю нужно будет только принять и игнорировать eip
.
Конечно, все это в основном для развлечения и любопытства. На самом деле нет простого преимущества перед простым решением:
void patch(int a,int b,int c,int d)
{
receiver(a,b,c,d);
}
Сгенерированная сборка будет короче, чем наш стек, но потребуется стек на 16 байт, потому что значения копируются в новую область ниже patch()
стека.
(На самом деле сгенерированный gcc ассемблер выделяет 28 байтов в стеке, хотя он использует только 16. Я не уверен, почему. Может быть, дополнительные 12 байтов являются частью какой-либо схемы защиты от разрушения стека.)