Я хочу сделать следующее:
У меня есть функция, которая не является моей (она действительно не имеет значения здесь, но только сказать, что я не управляю ею), и что я хочу исправить так, чтобы она вызвала мою функцию, сохраняя список аргументов (переход не является опцией).
То, что я пытаюсь сделать, для помещения указателя вершины стека, как это было, прежде чем та функция вызвана, и затем назовите мой (как возвращение и сделайте снова то же самое, но с другой функцией). Это не работает прямо, потому что стек становится испорченным. Я полагаю, что, когда я делаю вызов, он заменяет обратный адрес. Так, я сделал шаг для сохранения обратного адреса, сохраняющего его в глобально переменная, и он работает, но это не в порядке, потому что я хочу, чтобы он сопротивлялся к recursitivy, и Вы знаете то, что я имею в виду. Так или иначе я - новичок в блоке так вот почему, я здесь.
Не говорите мне об уже сделанном программном обеспечении делать это, потому что я хочу сделать вещи моим путем.
Конечно, этот код должен быть компилятором и независимой оптимизацией.
Мой код (Если это больше, чем, что приемлемо, говорят мне, как отправить его):
// A function that is not mine but to which I have access and want to patch so that it calls a function of mine with its original arguments
void real(int a,int b,int c,int d)
{
}
// A function that I want to be called, receiving the original arguments
void receiver(int a,int b,int c,int d)
{
printf("Arguments %d %d %d %d\n",a,b,c,d);
}
long helper;
// A patch to apply in the "real" function and on which I will call "receiver" with the same arguments that "real" received.
__declspec( naked ) void patch()
{
_asm
{
// This first two instructions save the return address in a global variable
// If I don't save and restore, the program won't work correctly.
// I want to do this without having to use a global variable
mov eax, [ebp+4]
mov helper,eax
push ebp
mov ebp, esp
// Make that the stack becomes as it were before the real function was called
add esp, 8
// Calls our receiver
call receiver
mov esp, ebp
pop ebp
// Restores the return address previously saved
mov eax, helper
mov [ebp+4],eax
ret
}
}
int _tmain(int argc, _TCHAR* argv[])
{
FlushInstructionCache(GetCurrentProcess(),&real,5);
DWORD oldProtection;
VirtualProtect(&real,5,PAGE_EXECUTE_READWRITE,&oldProtection);
// Patching the real function to go to my patch
((unsigned char*)real)[0] = 0xE9;
*((long*)((long)(real) + sizeof(unsigned char))) = (char*)patch - (char*)real - 5;
// calling real function (I'm just calling it with inline assembly because otherwise it seems to works as if it were un patched
// that is strange but irrelevant for this
_asm
{
push 666
push 1337
push 69
push 100
call real
add esp, 16
}
return 0;
}
Печать (и имеет к):
Аргументы 100 69 1337 666
Править:
Код я тестирую следующее предложение Vlad (Все еще не работающий)
// A patch to apply in the real function and on which I will call receiver with the same arguments that "real" received.
__declspec( naked ) void patch()
{
_asm
{
jmp start
mem:
nop
nop
nop
nop
start :
// This first two instructions save the return address in a global variable
// If I don't save and restore the program won't work correctly.
// I want to do this without having to use a global variable
mov eax, [ebp+4]
mov mem, eax
push ebp
mov ebp, esp
// Make that the stack becomes as it were before the real function was called
add esp, 8
// Calls our receiver
call receiver
mov esp, ebp
pop ebp
// Restores the return address previously saved
mov eax, mem
mov [ebp+4],eax
ret
}
}
Вы должны один раз добавить esp, 8
и один раз добавить esp, 16
. Один из них, должно быть, неправ.
Редактировать:
О, я вижу, после add esp, 8
вы должны были удалить из стека ebp
нажали 2 инструкции раньше и вернулись адрес.
В [ebp + 4] должен быть обратный адрес вызова _tmain
.
Edit2:
вы можете выделить «внутреннюю» переменную примерно так:
call next
dd 0
next:
pop eax
mov [eax], yourinfo
Но все еще не ясно, зачем нам вообще нужно сохранять это значение.
Edit3: (удалено, ошибочно)
Edit4:
Другая идея:
__declspec( naked ) void patch()
{
_asm
{
call next
// here we temporarily save the arguments
dd 0
dd 0
dd 0
dd 0
next:
pop eax
// eax points to the first dd
// now store the args
pop edx
mov [eax], edx
pop edx
mov [eax+4], edx
pop edx
mov [eax+8], edx
pop edx
mov [eax+12], edx
// now we can push the value
mov edx, [ebp+4]
push edx
// now, push the args again
mov edx, [eax+12]
push edx
mov edx, [eax+8]
push edx
mov edx, [eax+4]
push edx
mov edx, [eax]
push edx
// now continue with the old code
// --------------------------------
// restore the arguments
push ebp
mov ebp, esp
// Make that the stack becomes as it were before the real function was called
add esp, 8
// Calls our receiver
call receiver
mov esp, ebp
pop ebp
// ----------------------------
pop edx
mov [ebp+4], edx
ret
}
}
Это решение переживает рекурсию, но не одновременное выполнение из двух разных потоков.
Во время нормального выполнения операнды функции вставляются в обратном порядке в стек. При выполнении кода операции call
процессор сначала помещает регистр EIP (или CS / IP) в стек. Это обратный адрес. Когда выполнение достигает функции, которую вы хотите заменить, акции выглядят следующим образом:
Return address 1
Operand 1
Operand 2
Operand 2
На этом этапе вы собираетесь вызвать свою собственную функцию, которая будет иметь такой стек:
Return address 2
Return address 1
Operand 1
Operand 2
Operand 3
Ваша функция должна будет знать, что в стеке есть дополнительный DWORD, поскольку он делает то, что вы хотите.С этим легко справиться, если вы также написали свою заменяющую функциональную сборку, просто добавляйте 4 всякий раз, когда ссылаетесь на ESP. Когда вы вызываете RET в своей функции, появляется первый адрес возврата, и выполнение возвращается к функции, которую вы заменяете. Стек снова будет иметь следующий вид:
Return address 1
Operand 1
Operand 2
Operand 3
Вызов RET в этой функции снова вытолкнет адрес возврата из стека и вернет управление вызывающей функции. Это оставляет ваши операнды в стеке, что приводит к повреждению. Я предлагаю вызвать RET с таким количеством операндов функции, как это:
RET 3
Это вытолкнет из стека 3 (в моем примере) или сколько бы там ни было операндов. Вот пара ссылок, которые могут оказаться полезными:
http://pdos.csail.mit.edu/6.828/2009/readings/i386/CALL.htm http://pdos.csail.mit.edu /6.828/2009/readings/i386/RET.htm
Я никогда не использовал C ++ для подобных вещей низкого уровня, поэтому я не буду вдаваться в подробности вашего примера, но в целом, если вы хотите перехватывать вызов и поддерживать рекурсию в вашей логике, у вас есть два варианта: либо скопировать весь кадр стека (параметры) и вызвать «подключенный» оригинал с новой копией, либо, если это невозможно, сохранить свой собственный небольшой стек для хранения исходное возвращаемое значение (например, в виде связанного списка с корнем) в структуре данных на основе TLS.
Следующие фрагменты кода были проверены с помощью 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, все, что останется, это вызов
и ret
, и получателю нужно будет только принять и проигнорировать eip
.
Конечно, все это в основном для развлечения и любопытства. На самом деле нет большого преимущества перед простым решением:
void patch(int a,int b,int c,int d)
{
receiver(a,b,c,d);
}
Сгенерированная сборка будет короче, чем наш stack-roll, но для этого потребуется на 16 байт больше стека, потому что значения копируются в новую область под патчем ()
кадр стека.
(На самом деле asm, сгенерированный gcc, выделяет в стеке 28 байтов, хотя он использует только 16. Я не уверен, почему. Возможно, дополнительные 12 байтов являются частью какой-то схемы защиты от разбивания стека.)