2010-04-12 2 views
6

Я хочу сделать следующее:Мессинга со стеком в сборке и C++

У меня есть функция, которая не моя (это на самом деле здесь не имеет значения, а просто сказать, что у меня нет контроля над это) и что я хочу исправлять так, чтобы он вызывал мою функцию, сохраняя список аргументов (прыжок не является опцией).

То, что я пытаюсь сделать, заключается в том, чтобы поместить указатель стека, как это было до того, как эта функция вызывается, а затем вызвать мою (например, вернуться назад и повторить одно и то же, но с другой функцией). Это не работает прямо, потому что стек становится испорченным. Я считаю, что когда я делаю вызов, он заменяет обратный адрес. Итак, я сделал шаг, чтобы сохранить обратный адрес, сохраняя его в глобальной переменной, и он работает, но это не нормально, потому что я хочу, чтобы он сопротивлялся рекурсивию, и вы знаете, что я имею в виду. Во всяком случае, я новичок в сборке, поэтому я здесь.

Пожалуйста, не говорите мне о уже сделанных программах, чтобы сделать это, потому что я хочу сделать все по-своему.

Конечно, этот код должен быть независимым от компилятора и оптимизации.

Мой код (если он больше, чем то, что является приемлемым, пожалуйста, скажите мне, как разместить его):

// 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

Edit:

Код я проверяю следующее Владом предложение (Sti LL не работает)

// 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 
    } 
} 
+5

без явного вызова в asm, ваша функция, скорее всего, встроена компилятором. поэтому его не называли. – Vlad

+1

Спасибо за то, что заставили меня чувствовать себя плохо, не думая, что: D. Просто шучу. Спасибо, имеет смысл. – user246100

+0

Ваша функция 'real' действительно пуста? если это так, оно может быть короче 5 байтов, поэтому ваш патч может перезаписать следующую функцию. – Vlad

ответ

0

Вы когда-то add esp, 8 и один раз add esp, 16. Один из них, должно быть, ошибается.

Edit:
О, я вижу, после 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 
} 
} 

Это решение выживает рекурсию, но не одновременное выполнение от 2-х разных потоков.

+0

№. Одно из перемещений после нажатия, другое после Как я уже сказал в комментарии: код работает.Я просто хочу избавиться от глобальной переменной. – user246100

+0

@ user246100 Ну, иногда сборки отладки включают в себя код для исправления стека ... – Joshua

+0

I t отлаживается в отладке, выпускает с оптимизацией O2 и без него, и он работает во всех отношениях. Фактически, это была боль, чтобы заставить его работать с оптимизацией, но попробуйте сами. – user246100

0

Я никогда не использовал C++ для низкоуровневых вещей, подобных этому, поэтому я не буду вдаваться в особенности вашего примера, но в целом, если вы хотите перехватить вызов и иметь рекурсию поддержки логики, у вас есть два варианта : Либо скопируйте весь стек стека (параметры), либо вызовите исходный код с помощью новой копии или, если это невозможно, сохраните свой собственный небольшой стек для хранения исходного возвращаемого значения (например, в качестве связанного списка) в TLS основанной на данных.

0

Во время нормального выполнения операнды функции перемещаются в обратном порядке в стек. При выполнении кода операции 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

2

Следующие фрагменты кода были проверены с MinGW-г ++, но он должен работать в 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 

приемника.куб.см:

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, если вы удалите его из ассемблера все, что остается, является call и ret, и приемнику нужен только принять и игнорировать eip.


Конечно, все это в основном для удовольствия и любопытства. Там действительно нет главного преимущества над простым решением:

void patch(int a,int b,int c,int d) 
{ 
receiver(a,b,c,d); 
} 

Сформированный сборка будет короче, чем наш стек-н-ролл, но потребуется 16 байт стеки, так как значения копируются в свежую область ниже patch() Стек кадра.

(На самом деле НКА сгенерированных ASM выделяет 28 байт в стеке, даже если он использует только 16. Я не знаю, почему. Возможно, дополнительные 12 байт являются частью некоторых стека разбивая схемы защиты.)

Смежные вопросы