glTest1: Problema con la convención de llamada

Iniciado por 85, 7 Marzo 2013, 03:31 AM

0 Miembros y 1 Visitante están viendo este tema.

85

NIVEL: Beginner
Test: WinXP SP3

Hola de nuevo,
Bueno voy a contar una experiencia que le puede servir de ayuda si les pasa algo parecido.
Cuando estaba armando el proyecto para este tema:
http://foro.elhacker.net/programacion_cc/gltest_1_interceptar_opengl32_con_gpa-t384878.0.html

Antes de publicarlo, había tenido un problema con la convención de llamada de una función. En realidad se trata de un error mio en el código, pero que había pasado desapercibido y cuando el programa se cerraba no entendía por qué, lo que me llevó a tener que depurarlo.

Primero muestro la imagen del error en el código:


Opengl32 es __stdcall y al no especificarlo en el puntero, la llamada iba a ser __cdecl, que es la que usa el compilador VC++ por defecto. (aunque hay una opción para cambiar esto).

Antes es importante entender las características de las dos convenciones de llamada involucradas.

__cdecl
http://msdn.microsoft.com/en-us/library/zkwh89ks(v=vs.80).aspx

__stdcall
http://msdn.microsoft.com/en-us/library/zxk0tw93(v=vs.71).aspx

Esto da más información
http://en.wikipedia.org/wiki/X86_calling_conventions
http://www.codeproject.com/Articles/1388/Calling-Conventions-Demystified

Y esto tiene que ver directamente con la pila, por lo que si no saben acerca de ella, es una estructura con un ordenamiento LIFO y es usada por el programa para guardar direcciones. Las direcciones son de parámetros, de retorno, y de variables locales. Los registros del procesador relacionados a la pila son ESP y EBP (Apuntadores).
No menciono SS que es de segmento, sólo ESP y EBP.
EBP es el registro que se usa para referenciar la base de la pila,
ESP apunta siempre al tope de la pila. El tope de la pila es la última posición,  no la que le sigue..
ESP se modifica automáticamente para agrandar o achicar la pila (Es el puntero de pila).  EBP cambia según el 'stack frame' actual, pero siempre se guarda una copia del frame anterior. No cambia dentro del frame como lo puede hacer ESP, ya que es necesario para referenciar la base de la pila. Pero el programador si puede alterar EBP, y en esta demostración se
trata de eso XD.

Esta es la representación de una pila (relativa a EBP , no a ESP)

CitarHIGH MEMORY (crece de arriba hacia abajo)
EBP+18h   Quinto Parámetro
EBP+14h   Cuarto Parámetro
EBP+10h   Tercer Parámetro
EBP+0Ch   Segundo parámetro
EBP+08h   Primer Parámetro
EBP+04h   Direccion de retorno
EBP+0     Valor original de EBP
EBP-4h    Primera variable local
EBP-8h    Segunda variable local  <-----------------------------> ESP apunta a la última
LOW MEMORY

En el caso de la función glBegin que tiene un parámetro sólo, entonces nuestra pila inicial cuando se llama a nuestro Hook de glBegin es así:
CitarHIGH MEMORY (crece de arriba hacia abajo)
EBP+08h   Primer Parámetro       //cmp dword ptr [ebp+0x8], 0x00000009 //mode
EBP+04h   Direccion de retorno   // return address (0x401318 (EXE))
EBP+0     Valor original de EBP // agregado
LOW MEMORY
El tema es que dentro de la función de hook de glBegin el compilador agregó código que modifica el EBP. Este es el código insertado:


push ebp
mov ebp, esp
...


de esa forma cambia la pila, porque se agregó un elemento más (EBP), y cambia EBP mismo.

Supongamos que ESP se desplaza 4 posiciones,
eso significa que si el primer parámetro es EBP+0x8, desde la perspectiva de EBP, es ESP+0x14 desde la perspectiva de ESP.

Un comentario, ESP cambia cuando se usan las instrucciones PUSH y POP

Citar*--ESP = value;   // push
value = *ESP++;   // pop

y también cuando vemos:
CitarADD ESP, X
o
CitarSUB ESP, X
, que se usan para agrandar y achicar la pila.

Este es el desensamblado del Hook a glBegin


Con respecto a la 2da imagen, vamos a hacer una explicación paso por paso:

1: se guarda el registro EBP anterior (porque se ha modificado)
2: se ejecuta el código intermedio de la función normalmente.
3: se restaura el EBP
4: se limpia el stack por el valor de lo que había demás en la pila hasta el momento,
  Ver en la imagen, se calcula 0x10 lo cual es (4*4bytes), osea los 4 PUSHs del
  principio. La pila ahora queda equilibrada con sus datos originales intactos. la dirección de retorno y el primer parámetro (tiene uno sólo glBegin).
5: Se utiliza un salto incondicional para ir hacia la función original.
  (No se usa CALL).

Lo de calcular cuanto se debe limpiar se hace obteniendo la información del depurador (OllyDBG) con el programa en ejecución, y obviamente traceando cada línea, y observando la pila. Yo en realidad lo hice con OllyDBG porque no sabía del error en el código (no me había dado cuenta), pero para la demostración me resulta más fácil observar el desensamblado directamente con este explorador PE, ahora que se lo que pasaba XD.

En conclusión, el arreglo se hacía con inline ASM en la función de reemplazo (Hook) de glBegin

Código (cpp) [Seleccionar]


int glBegin_saved_ebp;

///////////////////////////////////////////////////////////////////////////////////////////////////////////

void __stdcall HOOK_glBegin(GLenum mode)
{
//--------------------------------------
__asm mov eax, [esp+0xc] // calculado en esta posición
__asm mov glBegin_saved_ebp, eax // guardamos el ebp anterior
//--------------------------------------

if(!once){

printf("\n");
printf("DLL -> HOOK_glBegin: ebp guardado!\n");
system("pause");
once=true;
}

if (mode==GL_POLYGON)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);
   glColor3f(0, 0, 0);
}

//--------------------------------------
__asm mov ebp,glBegin_saved_ebp // restauramos el ebp
       __asm add esp,0x10 // limpiamos stack (calculado como 4 * 4 bytes = 0x10)
       __asm jmp pOrig_glBegin // saltamos a la original
//--------------------------------------
//(*pOrig_glBegin)(mode);
}


Otra información que es importante conocer:
Prólogo y Epílogo
http://msdn.microsoft.com/es-ar/library/tawsa7cb(v=vs.80).aspx
http://msdn.microsoft.com/es-es/library/8ydc79k6(v=vs.80).aspx
http://msdn.microsoft.com/es-es/library/t2wt9aez(v=vs.110).aspx
http://msdn.microsoft.com/es-es/library/21d5kd3a.aspx

Más información
http://en.wikipedia.org/wiki/Call_stack
http://es.wikipedia.org/wiki/Pila_de_llamadas
http://securityetalii.es/tag/marco-de-pila/
http://learnassembler.com/procedim.html
http://msdn.microsoft.com/es-es/library/5sds75we(v=vs.90).aspx
http://www.pmzone.org/chapter06.html

"ENTER" y "LEAVE" para crear y liberar el stack frame.
http://en.wikipedia.org/wiki/X86_instruction_listings#Added_with_80186.2F80188

Así que todo resultó ser un problema del código, que faltaba __stdcall, no era necesario ningún arreglo con ASM XD.
Pero espero que haya resultado instructivo al menos.



Saludos

CODE VC++6
http://www.mediafire.com/?xzxvrc3ri3toz9t
Me cerraron el Windows Live Spaces, entonces me creé un WordPress XD
http://etkboyscout.wordpress.com/

85

#1
Voy a poner algo más a continuación para que no se haga tan largo el primer posteo XD, acerca del arreglo (FIX) con ensamblador en línea, yo había dicho que no se podía hacer con CALL, en realidad quise decir que no se podía hacer DIRECTAMENTE así como estaba la pila en esa situación.
Pero si arreglamos la pila si es posible de hecho lo acabo de hacer, y me pareció muy instructivo mostrárselos , veamos

Otra cosa, para hacer algo tan sencillo como esto quizás no haga falta tener que utilizar el depurador (OllyDBG por ejemplo), quiero decir, mucha gente que conozco nunca duda en ir directamente al OllyDBG si se trata de depurar algo, pero se que muchos programadores no recurren a este tipo de depuración DINÁMICA por así decirlo, sino más bien se quedan con una depuración NO DINÁMICA por así decirlo también, que se trata de solamente hacer que el programa se detenga y arroje resultados por pantalla, por archivo de texto o por MessageBox.
Para que se detenga el programa (lo que equivale a BreakPoint en el depurador), se puede usar un simple 'system("pause")', o un MessageBox que interrumpe el programa.

Volviendo al tema, sin necesidad de OllyDBG en esta ocasión, arreglé el code para que funcione con CALL. Este es el código que fue depurado con logs de información:

Código (cpp) [Seleccionar]

// DEPURAMIENTO POR MEDIO DE LOGs
int test1,test2,test3,test4,test5;
///////////////////////////////////////////////////////////////////////////////////////////////////////////

void __stdcall HOOK_glBegin(GLenum mode)
{
__asm mov eax, [esp+0x0] // calculado en esta posición
__asm mov glBegin_saved_ebp, eax // guardamos el ebp anterior
//////////////////////////////////////////////////////////////////

if(!once){

printf("\n");
printf("DLL -> HOOK_glBegin: ebp guardado!\n");
system("pause");
once=true;
}

if (mode==GL_POLYGON)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);
   glColor3f(0, 0, 0);
}

//HIGH MEMORY (crece de arriba hacia abajo)
//EBP+08h   Primer Parámetro       //cmp dword ptr [ebp+0x8], 0x00000009 //mode
//EBP+04h   Direccion de retorno   // return address (0x401318 (EXE))
//EBP+0     Valor original de EBP // agregado
//LOW MEMORY

__asm mov eax, [ebp+0x4]
__asm mov test1, eax
__asm mov eax, [ebp+0x8]
__asm mov test2, eax

// printf("ret: 0x%X  arg: 0x%X\n",test1, test2);
// system("pause");

//__asm mov eax, ebp
//__asm mov test5, eax
//printf("test5: 0x%X  ebp: 0x%X\n",test5, glBegin_saved_ebp);
//system("pause");

//__asm add esp, 0x14 //0x10= deja esp en ret
__asm add esp, 0xc //deja esp sin los 3 valores iniciales (lista para recargarla)

   //__asm mov eax, [esp+0x0]
//__asm mov test5, eax
//printf("test5: 0x%X\n",test5);
//system("pause");

__asm mov ebp, glBegin_saved_ebp
__asm push test2
__asm call pOrig_glBegin
// __asm add esp, 0x4// llamada a glBegin (__stdcall), se encarga de limpiar la stack

//__asm mov eax, [ebp+0x4]
//__asm mov test3, eax
//__asm mov eax, [ebp+0x8]
//__asm mov test4, eax
//printf("ret: 0x%X  arg: 0x%X\n",test3, test4);
//system("pause");

//__asm mov eax, ebp
//__asm mov test5, eax
//printf("test5: 0x%X  ebp: 0x%X\n",test5, glBegin_saved_ebp);
//system("pause");


//__asm mov ebp, glBegin_saved_ebp
__asm push test1
//__asm ret 0x4
__asm ret

//__asm add esp, 0x4
//__asm jmp test1
//(*pOrig_glBegin)(mode);
}


En realidad no estoy diciendo que no se use OllyDBG, estoy diciendo que para una pabada como esta no hace falta, depende de lo que haga falta.
Por ejemplo, para este caso necesitamos saber los valores de ESP y EBP, y
de algún registro que usemos para guardar datos, por ejemplo EAX.
Aparte siempre tengo un explorador PE o un desensamblador a mano.

Como ya había dicho, la situación de la pila era algo así:
HIGH MEMORY (crece de arriba hacia abajo)
EBP+08h   Primer Parámetro       //cmp dword ptr [ebp+0x8], 0x00000009 //mode
EBP+04h   Direccion de retorno   // return address (0x401318 (EXE))
EBP+0     Valor original de EBP // agregado (push ebp)
LOW MEMORY


con lo cual para llamar a CALL se debe pasar el parámetro de la función,  y CALL pone la dirección de retorno en la pila y al ejecutarse la función, se hace el ret limpiando dicha dirección. Como se trata de glBegin que es __stdcall, la propia función limpia el parámetro que estaba en la pila, por eso en realidad se hace ret 0x4.

El tema es que para llamar a CALL antes debía arreglarse la pila, por lo cual se borraron los 3 elementos iniciales (add esp, 0xC).
Luego se hace el CALL ya mencionado, se restaura el EBP modificado y por último se debe poner la dirección de retorno en la pila y hacer un ret. Fin de la historia.

Código (cpp) [Seleccionar]

int glBegin_saved_ebp;
int retaddress, param1;

///////////////////////////////////////////////////////////////////////////////////////////////////////////

void __stdcall HOOK_glBegin(GLenum mode)
{
//--------------------------------------
__asm mov eax, [esp+0x0] // calculado en esta posición
__asm mov glBegin_saved_ebp, eax // guardamos el ebp anterior
//--------------------------------------

if(!once){

printf("\n");
printf("DLL -> HOOK_glBegin: ebp guardado!\n");
system("pause");
once=true;
}

if (mode==GL_POLYGON)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);
   glColor3f(0, 0, 0);
}

__asm mov eax, [ebp+0x4]
__asm mov retaddress, eax // copia de retaddress
__asm mov eax, [ebp+0x8]
__asm mov param1, eax // copia del parámetro 1
__asm add esp, 0xc //deja stack sin los 3 valores iniciales (lista para recargarla)

__asm mov ebp, glBegin_saved_ebp // restaura ebp
__asm push param1 // pasa parámetro 1
__asm call pOrig_glBegin // guarda dirección de retorno en stack y luego se hace pop en el ret
// __asm add esp, 0x4// llamada a glBegin (__stdcall), se encarga de limpiar la stack (ej: ret 0x4)

__asm push retaddress // pasa retaddress
__asm ret // vuelve al exe (pop retaddress)
//(*pOrig_glBegin)(mode);
}


Si quieren revisar el código nuevo:
http://www.mediafire.com/?bd45oh8qny515b2

Ya le encontré una utilidad a este ARREGLO o FIX con ASM. En otro momento voy a comentar de que se trata.


Hasta Luego


Me cerraron el Windows Live Spaces, entonces me creé un WordPress XD
http://etkboyscout.wordpress.com/