|
Identificando Funciones, Argumentos, y Variables (Rhayader) |
�ste es mi primer ensayo de desensamblado. Un documento bastante largo escrito para un newbie en listado muerto. Este ensayo no es un documento todo-en-uno. En este ensayo acometo s�lo la identificaci�n de funciones. Tuve la primera idea de escribir este ensayo, cuando vi el ensayo de 'The Sandman' sobre el listado muerto que encontr� en "Invirtiendo C�digo Para Principiantes ". Si no lo has le�do, te consejo que primero lo leas. Aunque el ensayo este dirigido hacia el listado muerto, alguna de la informaci�n tambi�n puede aplicarse a la aproximaci�n viva. Espero que este ensayo te ayude en tu sesi�n desensamblando cualquier cosa ;) Y si tuvieras cualquier comentario, sugerencia, o si encuentras alg�n error en este ensayo, realmente apreciar�a, que me lo mandaras por correo. De esa manera, podr� revisar este ensayo para ofrecer un servicio mejor para todos nosotros. Para mantenerlo simple, lo enfoqu� favorablemente con un programa del lenguaje de programaci�n 'C', que se ejecuta en entorno Win32. Hay dos razones, por lo qu� hice esto. Primero, un programa Win32 escrito con el Lenguaje C ser� el programa m�s com�n que nos encontraremos y usaremos. Segundo, enfocando el modelo de memoria 32-bit, puedo evitar la complejidad del modelo de memoria segmentado. Antes de entrar en la discusi�n sobre c�mo una funci�n del alto nivel se relaciona en el listado de Ensamblador, hablaremos primero de la pila y la instrucci�n de llamada. Despu�s de todo, yo escribo este tutorial para un principiante en listado muerto. Si ya tuvieras bastante conocimiento sobre estos dos, puedes salt�rtelo, e ir a Identificando Funci�n. The Stack (La Pila)La pila es un �rea de memoria usada por un programa para guardar variables temporales. Cuando empiezas un programa, el sistema operativo buscar� un segmento de pila. Luego, �l pondr� la direcci�n de segmento de pila en SS y (E)SP para apuntar al primer byte detr�s del segmento de pila. Un compilador de lenguaje de alto nivel, tambi�n usar�a este �rea para sostener algunos argumentos de la funci�n y las variables locales. La pila, tiene las propiedades de una pila de platos. Es decir, el �ltimo que pusiste, es el primero que tomas. Pero, a diferencia de una pila de platos que se sostiene muy bien por gravedad, la cima de la pila tiene una direcci�n m�s baja que la base de la pila. En otras palabras: la pila crece descend�ntemente, como una pir�mide. Cuando un valor se agrega a la pila, el indicador de la pila ser� disminuido. Tambi�n podr�as querer saber, que hay dos maneras en las que un CPU almacena un valor en memoria.La CPU Intel es Little Endian (solo). Las palabras Little Endian se derivan de las palabras Little End In. Lo cu�l significa, que el extremo peque�o (byte m�s bajo) de un valor multibyte se guardar� primero. Por ejemplo, un valor de 0x1234, se guardar� en memoria como 0x3412. De igual manera, un valor de 0x5678ABCD se guardar� como 0xCDAB7856. Esta regla tambi�n se aplica a la pila. Normalmente, esto no ser� un problema. Pero, desde que nosotros peque�os crackers descarguemos la memoria dentro de nuestro debugger, necesitaremos saber esto y mentalmente poner en orden el valor que vemos en memoria. Registros Relacionados con La pila
|
Las instrucciones siguientes (no es una lista completa) permiten el uso de la pila para guardar o recuperar datos: PUSH, PUSHF (push flags), PUSHA (push all word register), PUSHAD (push all double word register), POP, POPF (pop flags), POPA (pop all word register), y POPAD (pop all double word register). Yo cubrir� s�lo la dos instrucci�n b�sica: PUSH y POP.
La sintaxis para PUSH es:
PUSH fuente
La instrucci�n PUSH disminuir� el valor de (E)SP por el tama�o de la fuente, y copiar� el valor de la fuente a [SS:(E)SP], borrando el valor anterior. En un programa 16-bit, la fuente debe ser un valor word. En programas 32-bit, la fuente puede ser word o dword. La fuente puede ser un registro (como EAX), una situaci�n de memoria, o un valor inmediato.
La sintaxis para POP es:
POP destino
La instrucci�n POP copiar� (no mover�) el valor de SS:(E)SP, y entonces disminuir� (E)SP por el tama�o de destino. El destino puede ser un registro o una situaci�n de memoria.
Observa c�mo trabajan las dos instrucciones. Sup�n que tenemos �sta instrucci�n:
push 1 push esi push edi push 2 ... pop eax pop edi pop esi
Despu�s de ejecutada toda la instrucci�n PUSH, la pila aparecer� as�:
(valor de memoria m�s alto)
higher memory value 1 esi edi ESP -> 2 lower memory value (valor de memoria m�s bajo)
Y despu�s de ejecutada toda la instrucci�n POP,
(valor de memoria m�s alto)
higher memory value ESP -> 1 esi edi 2 lower memory value (valor de memoria m�s bajo)
con,
eax = 2 esi = pushed esi edi = pushed edi
Espero que puedas seguir esta introducci�n r�pida a la pila.
Aunque los desensambladores modernos como W32DASM e IDA Pro marcar�n una funci�n, hay veces que no podr�n encontrarlo. Por ejemplo una funci�n que se dirige a trav�s de un indicador. Por lo tanto, todav�a necesitamos saber, c�mo el compilador traduce una funci�n del lenguaje de alto nivel en su equivalente en lenguaje ensamblador.
Muy b�sicamente, la instrucci�n call simplemente es un modo de transferir el control a otra parte de un programa. Simplemente como un jmp, o jcondicional. Pero a diferencia de un salto que es un traslado permanente de control, la llamada almacena la informaci�n de retorno, as� cuando la rutina llamada termina por una instrucci�n de ret, el programa puede retroceder al "llamador" (parte del programa que realiza la llamada). Sabiendo esto, est� claro para nosotros que el �xito de una instrucci�n de llamada depende de las mec�nicas para guardar y recuperar la informaci�n de retorno. A menos que el objetivo de la llamada sea un TSS (task state segment) [ n. del t.: Estado de las tareas del segmento] o una tarea puerta, las mec�nicas son bastante simples.
La rutina llamada vuelve a su llamador grabando la direcci�n de la instrucci�n de su llamador. La instrucci�n de llamada hace esto grabando el valor del registro (E)IP antes de que salte dentro de la rutina llamada. Como probablemente sabr�s, (E)IP apunta a la instrucci�n que el CPU quiere ejecutar. Simplemente cambiando el registro (E)IP, puedes cambiar la manera en la que un programa se ejecutar�. La instrucci�n de llamada graba este valor empuj�ndolo hacia la pila. Ya que (E)IP se incrementa en cuanto una instrucci�n se saque, el valor que se empuja hacia la pila es el valor de (E)IP de la instrucci�n que sigue a la instrucci�n de llamada. En un programa 32-bit, toda llamada es una llamada cercana. En un programa 16-bit, donde a veces un traslado de intrasegmento se requiere, el programa necesita usar una llamada lejana. En una llamada lejana, el valor de CS:(E)IP necesita ser grabado. El registro de CS es empujado primero, entonces el valor de (E)IP es empujado sobre la pila. Cuando una rutina llamada se termina, la instrucci�n ret quitar� el valor de (E)IP (y CS para una llamada lejana) dentro de (E)IP (y registro CS).
La sintaxis de la llamada es muy simple. Sin tener en cuenta el objetivo, la sintaxis es la misma:
call address (Llamada direcci�n)
Un problema ocurre, cuando la llamada est� usando una direcci�n indirecta. Por ejemplo, call edi o, call [ebx+0Dh]. En el primer caso, nosotros podemos buscar la instrucci�n que asigna edi en una direcci�n antes de la instrucci�n de llamada. Normalmente �sta ser� una direcci�n del API de Windows. Para el segundo caso, puedes usar una aproximaci�n viva (usando debugger). Pon un breakpoint all�, y averigua el valor de ebx. O, una manera mejor, es equiparse con el conocimiento necesario para encontrarlo. Fravia+ ya escribi� un ensayo excelente sobre la tabla de relocalizaci�n de llamada. L�elo. Todo lo que yo puedo agregar es, con IDA Pro, puedes utilizar su funci�n Search In Core para buscar la tabla de relocalizaci�n de llamada. Hay veces, por supuesto, cuando el debugger es la �nica manera de buscarlo. Es casi imposible para nosotros encontrar a donde est� refiri�ndose la direcci�n en un programa de C++ que tiene toneladas de direcciones indirectas. Aqu� es donde W32DASM tienen una ventaja sobre IDA Pro. Con su establecimiento en debugger, encontrar la direcci�n ser�n muy r�pido.
Si una instrucci�n de llamada identifica un punto de entrada de funci�n, entonces tendr� sentido para nosotros si la instrucci�n ret identifica un punto de retorno de funci�n. B�sicamente, la instrucci�n de ret devolver� al llamador colocando el apropiado (E)IP (y CS para una llamada lejana). Dependiendo del tipo de la llamada (y del objetivo), los ret pueden ser retn (retorno cercano), retf (retorno lejano), o iret (involucrando conmutaci�n de tareas ). Una instrucci�n de ret s�lo puede aparecer sin el sufijo n o f si se escribe dentro de un proc director. Buenos desensambladores, como IDA Pro que desmontan correctamente un programa en su c�digo en ensamblador pondr� normalmente una instrucci�n ret dentro de un proc director. Entonces, es el tipo del proc (cercano o lejano) quien define el tipo de ret.
La instrucci�n ret puede especificar un par�metro num�rico que identificar� cu�ntos bytes deben quitarse de la pila despu�s de que se ha hecho saltar la direcci�n de retorno. Si ves un c�digo as�:
some_procedure proc near ... ... pop edi pop esi add esp, 10h pop bp ret 8 SOME procedure endp
Asumiendo que some_procedure ha quitado correctamente todos los valores que puso a la pila, el estado del llamador ser� as�:
... ; esp apunta a xxxxxxxx push 32bit_variable ; esp apunta a 32bit_variable push another_32bit_variable ; esp apunta a another_32bit_variable call some_procedure inc edi ; la instrucci�n de ret nos traer� aqu� ; y el puntero esp retrocede a xxxxxxxx ...
Determinar una salida de la funci�n puede ser m�s complicado. Si el compilador perfeccionado esta encendido, puede haber varios lugares donde la funci�n hace un ret a su llamador. Normalmente, siempre que una funci�n termine, habr� un comienzo de otra funci�n. Podemos verificar donde acaba una funci�n, aun cuando tenga ret m�ltiples, mirando la instrucci�n que sigue a la instrucci�n ret. Cuando el desensamblador puede marcar la pr�xima funci�n correctamente, esto probablemente no ser� un problema. Pero en caso de que �l no lo marque apropiadamente, tendremos que buscar algo que se parezca a un pr�logo de la funci�n.
El pr�logo normal generado por un compilador, ser� alguna variaci�n de �stos:
Guarda el original (E)BP en la pila.
Asigna (E)SP (indicador de la pila) al registro (E)BP.
Disminuye el indicador de la pila, para hacer sitio para la variable local.
Salva llamando al registro de funci�n variable hacia la pila.
Expresado en lenguaje ensamblador, se parecer� a esto (32-bit):
push ebp ; Guarda el marco de EBP del llamador mov ebp, esp ; Prepara nuevo marco de EBP sub esp, xxxx ; xxxx es el n�mero de bytes necesario para la variable local push esi ; Guarda el registro del llamador push edi ...
En un programas de C/C++, la funci�n llamada debe conservar el siguiente registro: (E)SI, (E)DI, (E)BP, (E)SP, CS, DS, SS. Es decir, la funci�n llamada no podr�a cambiar el valor del registro anterior. Si la funci�n llamada necesitara �stos registros (ESI y EDI ser�n los m�s empleados normalmente), debe guardarlos primero dentro de la pila. Sin embargo, la funci�n llamada puede usar: (E)AX, (E)BX, (E)CX, (E)DX, y ES libremente, sin la necesidad de guardarlos primero. El compilador los guardara en el pr�logo.
Nota sobre ENTER y LEAVE: Dos instrucciones especiales se agregaron en los 80286 y en procesadores posteriores acomodados a lenguajes de alto nivel que requieren un marco de pila al llamar subrutinas: ENTER y LEAVE. Si los 80286 o posteriores est�n habilitados, el compilador puede optar por usar esta instrucci�n. Busca: enter xxxx, 0h ; xxxx es el n�mero de bytes necesitado por la variable local push esi push edi ... El n�mero 0h, en la instrucci�n ENTER, es el n�mero del nivel. En nivel 0, enter crear� un marco de pila siguiendo estos pasos: push ebp mov ebp, esp sub esp, xxxx Si el nivel esta sobre 0, ENTER guardar� el (E)BP padre primero como un v�nculo posterior, y el nivel superior (E)BP en orden, y acabando con el actual (E)BP. Esto hace f�cil para un programa alcanzar la variable del nivel m�s alto. Cuando la funci�n busca la variable de nivel m�s alto, busca un c�digo similar a esto: mov esi, [ebp-4] ; Obtiene el nivel m�s alto (E)BP. ; El pr�ximo nivel (E)BP es [ebp-8]. mov eax, ss:[esi-8] ; Obtiene la primera variable de nivel m�s alto. ; Si la primera variable es valor 32-bit, la segunda ; variable estar� en [ebp-C] Es bastante raro encontrar un programa que usa ENTER en un nivel sobre 0. Un programa que este compilado con Clari�n probablemente sea una excepci�n a esto. Como puedes ver por la descripci�n anterior, ENTER con nivel sobre 0 requiere m�s espacio en el marco de pila. La instrucci�n LEAVE eliminara simplemente el marco de pila actual de la pila, restaurando el anterior (E)BP y (E)SP. |
Es esencial que todos los valores puestos sean limpiados antes del retorno de la funci�n a su llamador. De lo contrario, la instrucci�n ret har� saltar un valor equivocado a (E)IP. Por esta raz�n, una funci�n ep�logo se parecer� mucho al c�digo pr�logo. S�lo esta vez, har� lo inverso de lo que el pr�logo hizo. Para nuestro ejemplo anterior, el c�digo podr�a parecerse a esto:
.... pop edi pop esi add esp, xxxx pop ebp ret
o, utilizando la instrucci�n LEAVE:
.... pop edi pop esi leave ret
El c�digo escrito anteriormente es de golpe un tipo completo, pr�logo y ep�logo. En el mundo real, alguno puede faltar. Si el programa no necesita ninguna variable en absoluto, el compilador puede que no se moleste en configurar un marco de pila. En un programa 32-bit, incluso cuando una funci�n utiliza una variable local, el compilador aun podr�a no configurar un marco de pila en ebp. El modo de direccionar de 32-bit le permite al compilador usar ESP para dirigir argumentos y variables locales. Y, por supuesto, siempre es una posibilidad que el programa este escrito con lenguaje ensamblador. Con este lenguaje, todas las apuestas est�n cerradas. El programador tiene control completo.
Este material probablemente no es para alguien que s�lo empieza su aventura en listado muerto. Sin embargo, puesto que muchos de los programa de estos d�as est�n usando MFC, y varios compiladores ya lo soportan (incluso Watcom y Symantec), yo pienso que es una cosa importante de saber. Puedes saltarte esto si quieres.
Hay varias maneras para encontrar la informaci�n. Usando un editor hexadecimal, un "dumpeador" para descargar la secci�n de .rdata, o, puedes usar la funci�n Search In Core de IDA pro. Tambi�n ayudara, si entiendes lo que es un trazado del mensaje. No lo discutir� aqu�. Si no eres programador de MFC, puedes leer sobre eso en este art�culo de Visual C++ Developer Journal's escrito por George Shepherd y Scot Wingo.
El trazado del mensaje que nosotros tenemos est� despu�s de AFX_MSGMAP_ENTRY. Est� definido como: struct AFX_MSGMAP_ENTRY { UINT nMessage; UINT nCode; UINT nID; UINT nLastID; UINT nSig; AFX_PMSG pfn; }; El primer campo identifica el mensaje de Windows que est� viniendo del sistema. La definici�n de los mensajes es igual que el SDK. El mensaje m�s importante para nuestro prop�sito es WM_COMMAND que es definido como 0111h, y es enviado por Windows cada vez que nosotros pulsamos el bot�n de un men�, o un bot�n. El segundo campo representa el c�digo de WM_NOTIFY. El tercer campo es la ID del control de arranque, y el cuarto campo es la �ltima ID del control. Si el control est� en serie ( ej. grupo de botones de radio, men� en cascada), el primer art�culo estar� en nID, y el �ltimo art�culo en nLastID. El quinto campo es la firma de la funci�n para manejar el mensaje. Y el �ltimo campo es un indicador para la funci�n del manipulador del mensaje. Sabiendo esto, ahora podemos localizar una funci�n para un bot�n en particular o men�. La primera cosa que necesitas hacer es extraer la fuente del ejecutable que estabas desmontando. Yo utilizo el editor de Visual C++ para hacer esto. Localiza el recurso en el que estabas interesado, con el, y toma nota de su ID. Si quieres seguir el ejemplo, tienes que desmontar un programa MFC. Hazlo con cualquier programa MFC. Por ejemplo, quieres saber qu� funci�n utiliza un programa para su men� guardar. Mirando a trav�s de tu editor de fuente, encuentras que la ID del men� Guardar del objetivo es 57603. En IDA Pro, ve a la secci�n de .rdata (View, Segment, y selecciona .rdata de la lista). Entonces, usa la funci�n "Search for Text in Core..." de IDA (Alt+B). Aseg�rate que busca hacia "abajo." Si no es as�, pulsa el bot�n cancel, y utiliza la tecla TAB para cambiar la direcci�n de la b�squeda. Teclea 57603 para buscar el string, pulsa el bot�n decimal, y pulsa el bot�n OK. IDA se detendr� en algo similar a esto:
Convierte el valor de la situaci�n del cursor a un dword decimal. La manera m�s r�pida de hacerlo es apretando "o" seguido por "h" ("o" lo convertir� a un offset dword, "h" convertir� el offset a un decimal dword. No es la manera correcta, pero funciona, y tambi�n m�s r�pidamente :). Debes verlo como 57603. Si no es as�, entonces contin�a buscando. Si es as�, entonces intenta convertir el valor de antes y despu�s de �l. Debes ver algo similar a esto:
En offset 423280, encontrar�s la funci�n que manipula este mensaje. Pero, eso no es el final. Puesto que una ID similar podr�a ser usada varias veces, por clases diferentes, tendr�s que continuar tu b�squeda hasta que ya no est�s en el segmento de .rdata. Despu�s de que buscas todas las ocurrencias de este ID, puedes verificar que estabas mirando el lugar correcto examinando la sucesi�n de bytes circundante. Convi�rtelo de manera similar. Si estuvieras buscando un bot�n en un di�logo, entonces ver�s que la ID de otro bot�n que este en el mismo di�logo estar� alrededor de �l. Cuando buscas un men�, recuerda, si un programa tuviera clases diferentes de este derivadas de CView, probablemente habr� m�ltiples lugares donde la misma ID del men� podr�a aparecer. Experimenta con eso. Quiz� alg�n d�a, en alguna parte, una herramienta como FRMSPY o EXE2DPR se desarrollar� para este prop�sito (�por ti, mi estimado amigo? ;). Pero hasta ese d�a, esta es la manera m�s r�pida que yo conozco. Es aun m�s r�pido que usando un debugger. Podr�as utilizar el comando stack de SoftICE para buscarlo. Pero, todav�a tendr�as que comparar varios resultado de stack para encontrar la situaci�n exacta. Yo todav�a soy un principiante en MFC no obstante. As�, si sabes otra manera, por favor m�ndame un mail, as� podremos incluirlo en estos tuts. Funci�n de Retorno de ValorNo hay mucho que decir sobre una funci�n de retorno de valor. En un programa 32-bit, la funci�n devuelve su valor en EAX. Un programa 16-bit usa AX para un valor 16-bit, y una combinaci�n de DX:AX para un valor 32-bit. Si, no obstante, el programa est� escrito en lenguaje ensamblador, la funci�n puede devolver su valor donde quiera. Una costumbre com�n en ensamblador es, si la funci�n es una funci�n booleana, la funci�n pondr� el flag de acarreo (CF) como apropiado. Si este es el caso, puedes mirar el c�digo que sigue a la llamada para un JC o JNC. Argumentos de la funci�nAntes de que nosotros nos metamos en la discusi�n sobre c�mo podemos reconocer un argumentos de la funci�n, debemos saber alg�n convenio para llamadas exteriores. Con la excepci�n del convenio de llamada para la llamada r�pida (fastcall), el compilador pasar� argumentos a una funci�n de llamada en la pila. Saber convenios de llamadas diferentes, ayudar� a que nosotros deduzcamos donde est�n los argumentos. Los convenios de llamadas dictan c�mo se pasan argumentos en una funci�n, en lenguaje ensamblador, y c�mo la pila se limpiar� cuando la funci�n retorne. Debajo est� la tabla de algunos convenios de llamadas.
|
Nota: Esta tabla est� sacada del art�culo de John Robbins en MSJ. Aunque el "Penguin guy" raramente cometi� un error, yo a�n verifico el resultado con mi compilador. Si quieres verificarlo con un compilador de C++, usa extern "C" para toda la funci�n, para prevenir C++ name mangling. Mi compilador (MSVC 4.2) tambi�n limita la capacidad de convenio de llamada que yo puedo hacer. No tengo ninguna manera de declarar una funci�n en modo Pascal. Si supieras cual es la funci�n de distintivo del nombre en modo pascal, entonces por favor m�ndame un e-mail. |
Explicar� las dos secciones m�s importantes para nuestro prop�sito: Pasando argumentos y Mantenimiento de la Pila. Comparado con, otras secciones que ya est�n escritas "en ingl�s":)
La secci�n Pasando Argumentos nos dice c�mo el argumento ser� empujado hacia la pila antes de que el programa llame a la funci�n. Si el argumento es empujado de izquierda a derecha, esa funci�n ser� declarada como:
some_function (0x1000, 0x2000, 0x3000);
parecer�a como:
push 1000h push 2000h push 3000h call some_function ...
Similarmente, si los argumentos son empujados de derecha a izquierda, parecer�a como:
push 3000h push 2000h push 1000h call some_function ...
Si el argumento es pasado usando el registro (e.j. modo fastcall):
push 3000h mov edx, 2000h mov ecx, 1000h call some_function ...
No debes esperar que la secuencia empujada vengan juntos uno despu�s de otro. Digamos, por ejemplo, que quieres asignar una memoria en el heap (*) de un programa Win32. Para hacer eso, nosotros podemos usar la funci�n HeapAlloc. Pero, en lugar de creando otro heap, en cambio nosotros queremos usar el heap del proceso. Podr�as codificarlo como:
(* n. del t.: Area especial en la memoria, que se utiliza para almacenar recursos importantes)
LPVOID lpMem = HeapAlloc(GetProcessHeap(), 0, 1024);
Y, en el
desensamblado, podr�a mostrarse como:push 400h ; argumentos de HeapAlloc push 0 ; argumentos de HeapAlloc call ds:GetProcessHeap ; GetProcessHeap no requiere ning�n argumento.As�,los dos ; valor empujado antes, es ignorado por la funci�n eax de push ; empuja el ret del MANIPULADOR de memoria de GetProcessHeap. ds:HeapAlloc de call ; ahora,los argumentos de HeapAlloc est�n completos,ll�malo mov [ebp-20], eax ; guarda el ret del indicador de LPVOID como variable local ... Ahora, para la secci�n de Mantenimiento de Pila. �Te acuerdas todav�a de nuestra discusi�n de la funci�n de ret, d�nde (E)SP es devuelto a su valor anterior, antes de que todo el argumento sea empujado? Bien, dicho simplemente, eso es lo qu� es el mantenimiento de la pila. Qui�n ser� responsable de poner el valor de (E)SP. Si la pila est� mantenida por la funci�n llamada, nosotros podr�amos buscar al final de la funci�n por un c�digo como este (en este ejemplo la funci�n es una funci�n 32-bit y tiene tres argumentos pasados a ella, ajustados a 32-bit cada uno): ...............................; esto es un c�digo 32-bit .........pop edi .........pop esi .........add esp, 20h .........ret 0Ch ..............; limpia la pila, quita 12 bytes de ella Si este es el llamador responsable del mantenimiento de la pila, el c�digo del llamador podr�a parecerse a esto: ... ; esto es un c�digo 32-bit push 3000h push 2000h push 1000h call some_functions add esp, 0Ch ; limpia la pila, quita 12 bytes de ella ... Pero, no debes esperar agregar esp, xxxx despu�s de cada llamada.A veces, cuando la funci�n llamada s�lo ten�a uno o dos argumentos, el compilador podr�a quitar simplemente la pila dentro del registro innecesario, como: ... ; esto es un c�digo 32-bit push 1000h ; s�lo un argumento call little_functions pop edx ; limpia la pila, quita 4 bytes de ella. ; edx contiene un valor innecesario mov edx, [ebp-20] ; prepara edx para otro prop�sito ... El mantenimiento de la pila nos ayuda a deducir cu�ntos argumentos, de la funci�n que nosotros est�bamos examinando, son usados (recuerda que en la sucesi�n empujada no siempre vienen uno despu�s de otro). Si una funci�n 32-bit quita 10h de la pila, entonces debe de haber tenido 4 argumentos. Un punto a recordar es: Todos los argumentos de la funci�n de API Win32 son 32-bit. Si examinas la secci�n de Mantenimiento de Pila, notar�s eso s�lo en los convenios de llamadas de C, que el llamador debe limpiar. Una de las razones es el hecho que en un programa de C una funci�n puede tener un n�mero variable de argumentos. La funci�n printf es un ejemplo de esto. Desde que sea imposible para la funci�n llamada saber por adelantado, cu�ntos argumentos se pasar�n a la funci�n, no podr� limpiar la pila dentro de la funci�n. Por lo tanto el mantenimiento de la pila es cargado al llamador. Esto tambi�n es la raz�n, de por qu� algunas API de Win32 usan convenios de llamadas de C en lugar de convenios de llamadas de stdcall. Podr�as echar una mirada a un programa que llama a wsprintf o a una funci�n similar. Cuando desensamblas un programa, y quiere saber donde est� puesto el argumento(s) para una funci�n, la primera cosa que tendremos que deducir es que convenios de llamadas usa la funci�n. Despu�s de que nosotros nos figuramos que convenios de llamadas usa la funci�n, el resto realmente es pan comido. Miremos un ejemplo realmente mundial (t� probablemente te cansar�s con push 1000h por ahora :). Un procedimiento de Di�logo en Win32: LRESULT CALLBACK AboutProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) Despu�s de que la funci�n ha ejecutado su c�digo pr�logo (es decir despu�s de mover ebp, esp ejecutado), la pila se parecer� a esto:
|
Si encontr�ramos un c�digo en nuestra funci�n que usa la direcci�n [EBP+08h], nosotros sabremos que es el que usa hDlg. Por lo tanto, nosotros podemos reemplazar [EBP+08h] con [hDlg]. Similarmente, nosotros podemos reemplazar [EBP+0Ch] con [msg]. El punto importante aqu� es (debes recordar esto):
Asimismo esto tambi�n es verdadero para c�digo 16-bit. S�lo que, en c�digo 16-bit, los argumentos son 16-bit de ancho, y si es una llamada lejana, el retorno de CS tambi�n ser� empujado a la pila. CS ser� empujado primero (en [BP+04h]), seguido por IP (en [BP+02h]). Identificando argumentos (y variables tambi�n), saber algunas pr�cticas de programaci�n en Win32 tambi�n es inmensamente �til. Por ejemplo, los programadores no lanzar�n un manipulador probablemente. Si la rutina del llamador que estabas examinando llam� a la funci�n de Win32 GetParent (), y pas� el resultado a una funci�n que nosotros est�bamos examinando, normalmente se quedar� como hwndParent a lo largo de la funci�n. Similarmente, un manipulador que es devuelto por la funci�n CreateFile (), se quedar� como un manipulador para el archivo. Si estas desensamblando para crackear, y la funci�n del llamador pas� hwndParent para preparar la caja de di�logo del registro, probablemente no encontrar�s la necesidad de examinar c�mo se usar�. En la pr�ctica, esto evitara que examinemos alguna funci�n que es llamada con este argumento. Por otro lado, un indicador puede lanzarse para otro tipo en la funci�n. Esto no aparecer� en nuestro desensamblaje. Para el lenguaje ensamblador, 32-bit son 32-bit, o es un punto para un string o un indicador para la estructura. As� que, probablemente quieras tener cuidado sobre renombrarlo. Ay, a veces no es tan simple. Con programas 32-bit, el compilador puede utilizar ESP para enviar argumentos, como [ESP+20h]. Cuando encuentras esto, debes tener mucho cuidado con renombrarlo. Porque el valor de ESP es cambiado siempre que un valor sea empujado o quitado de la pila. Un string que fue guardado en [ESP+20h] podr�a haber cambiado a [ESP+28h] despu�s, cuando la funci�n empuj� dos DWORD a la pila. Hay afortunadamente para nosotros, un desensamblador como IDA Pro. En IDA Pro, pueden renombrarse argumentos y variables locales dentro del desensamblador. Y, si es dirigido con EBP o ESP, IDA Pro siempre renombrar� el correcto. Identificando VariablesVariables localesLas variables locales de una funci�n tambi�n son guardadas en la pila. Habr� veces, que el compilador usar� los registros con dificultad para guardar las variables locales. Sin embargo, puesto que algunos registros son usados autom�ticamente en ciertas operaciones (como EDI en MOVSD, ECX en instrucciones que requieren contador, EAX en IDIV), el uso de registros para guardar las variables locales a lo largo de la funci�n ser� bastante raro. Una cosa importante a recordar es:
Debes recordar sin embargo, que si la
funci�n us� ESP para dirigir los argumentos de
funci�n, tambi�n usar� ESP para dirigir variables
locales. Cuando la funci�n usa ESP, ambos, argumentos y
variables locales tendr�n un desplazamiento positivo
desde ella. Al examinar variables locales, el convenio de
llamada usado por la funci�n no tendr� ninguna
diferencia. La excepci�n a esto es el convenios de
llamada naked (*), donde, los mismos
programadores escribir�n la funci�n en lenguaje
ensamblador. Determinar el tipo de las variables locales no es tan f�cil como determinar argumentos. Al trabajar con argumentos, nosotros sab�amos si eran 16-bit o 32-bit, examinando la instrucci�n push. Determinando un tipo de variable local, nosotros tenemos que mirar c�mo ser�n usadas las variables particulares. Despu�s te dir�, en este ensayo, c�mo podemos examinar el tipo de variables locales. Por supuesto podemos determinar cu�ntos bytes son usados para las variables locales por la funci�n. Cuando encontramos la instrucci�n sub esp, xxxx en el prologo de la funci�n, xxxx son el n�mero de bytes que la funci�n requiere para las variables locales. Variables GlobalesDeterminar las variables globales en una funci�n es f�cil. Las variables globales no necesitaron la ayuda de EBP o ESP para dirigirse. As�, si encontr�ramos una instrucci�n como: mov eax, [00421EB0] sabemos que la funci�n est� dirigi�ndose a una variable global. Sin embargo, determinar el tipo de las variables, ser� justo como determinar el tipo de variables locales. Tendr�s que averiguar c�mo nuestra funci�n lo usa. Incluso cuando tu desensamblador intente convertirlo al valor correcto (bas�ndose en la instrucci�n que el desensamblador encontr� cuando las variables globales son dirigidas), no siempre debes confiar en �l. Tu desensamblador simplemente est� intentando ayudar. No reemplazar� nuestros ojos, cerebro, y experiencias:) Al examinar un programa 16-bit, debes tener cuidado sobre renombrar una variable global. Tendr�s que asegurarte primero de que el segmento (DS o a veces ES) est� de hecho apuntando al correcto. Descubrimiento del Tipo de VariablesYa mencion� c�mo es de �til el API de Win32 al determinar el tipo de argumentos de la funci�n. Bien, tambi�n tiene exactamente el mismo poder para determinar el tipo de variables locales. Cuando estamos examinando qu� usa una funci�n de variables locales, Win32 API, debe ser lo primero que investiguemos en la funci�n. A veces, s�lo renombrando las variables que usa la funci�n con Win32 API, nuestros listado desensamblado aparecer� mucho m�s despejado. Considera el siguiente recorte: ... push offset 00423440 lea edx, [ebp-5B0h] push edx push offset 0041EC2C push offset 0041EC34 call ds:WritePrivateProfileStringA ... Volviendo dentro de nuestra documentaci�n del fiel API, sabemos que WritePrivateProfileString es declarado como: BOOL WritePrivateProfileString( LPCTSTR lpAppName, // indicador para el nombre de la secci�n LPCTSTR lpKeyName, // indicador para el nombre de la tecla LPCTSTR lpString, // indicador para string a agregar LPCTSTR lpFileName // indicador para el nombre del fichero de inicializaci�n ); Ahora sabemos que en offset 00423440, encontraremos un string literal para el nombre de fichero .INI, y la variable local [EBP-5B0h] ser� un string literal para el valor de la tecla a escribir. De igual manera, nosotros podemos reemplazar 0041EC2C y 0041EC34 con szKey y szSection respectivamente. Si los argumentos, puede usarse para identificar un tipo de la variable, el valor de retorno tambi�n puede usarse identificando variables. Echa una mirada a este ejemplo: ... call ds:_hread mov [ebp-40h], eax ... De la documentaci�n de API, nosotros supimos que _hread devolver� el n�mero de bytes le�do. Por nuestra discusi�n, nosotros supimos que el valor de retorno estar� en eax. As�, [EBP-40h] debe ser un valor dword que contiene la cuenta de lectura de los bytes.Probablemente querr�s renombrarlo como dwRead. Podr�an usarse muchas variables como variables temporales. Y c�mo lo uses, probablemente cambiar� en diferentes partes del programa. Por consiguiente, siempre debes verificar c�mo esas variables particulares que renombraste son usadas a lo largo del programa. Si es usado de forma consistente, entonces de esa manera, tu suposici�n ser� correcta probablemente. Recientemente, desensambladores como IDA Pro, toman esto en un grado superior. No s�lo nos mostrar� el API Win32. Con su tecnolog�a FLIRT, IDA Pro nos mostrar� tambi�n cuando la funci�n que nosotros examinamos usa el CRT (librer�a runtime de C), clases de MFC, funci�n de Delphi, o la funci�n de VCL de Borland. Incluso cuando est� est�ticamente unido. Esto es una fortuna de informaci�n por supuesto. Si trabajas mucho con programas de Delphi, podr�as querer bajarte la librer�a FLIRT para Delphi de la p�gina de download de Peter Sawatzki . Cuando la funci�n no est� usando un API conocido, las variables no pueden ser reconocidas f�cilmente. Un tipo de variables que podr�an reconocerse f�cilmente son las variables booleanas. Cuando el c�digo que estas examinando contiene una instrucci�n como: ... move eax, [ebp-40h] test eax, eax jz loc_41453D ... En [EBP-40h] probablemente encuentres una variable de tipo BOOL. Otro tipo que puede ser reconoci� f�cilmente, es un contador para un loop. En C, el c�digo para loop normalmente ser� codificado como: for (i = 0; i < 1024; i++) { // procesando algunas variables }; Por favor, examina cuidadosamente el siguiente recorte. Presta atenci�n a c�mo [EBP-164h] es usado: ... mov dword ptr [ebp-164h], 0 ; Inicia [ebp-164h] jmp short loc_41453D ; Empieza desde loc_41453D loc_41452E: mov ecx, [ebp-164h] ; [EBP-164h] de nuevo inc ecx ; incrementa mov [ebp-164h], ecx ; y guarda loc_41453D: cmp dword ptr [ebp-164h], 400h ; �Es [ebp-164h] > 1024 ? jnb short loc_414567 ; Si mayor, salta ... ; Algunos c�digos con los que trabajan ... ; otras variables ... ; Recorte para brevedad jmp short loc_41452E ; salto atr�s loc_414567: ... �Supiste c�mo funciona? Tienes raz�n, [EBP-164h] es el contador. Si no eres un programador de C, probablemente no sabr�s una cosa graciosa. Un programador de C, raramente usa un contador para un prop�sito diferente. Si encuentras uno para una instrucci�n dentro de tu desensamblaje, muchas veces puedes apostar que las mismas variables se usar�n de nuevo para un contador cuando la funci�n hace otro para un loop. Una cosa para lo que lo usar�n los programadores, es para un �ndice probablemente para un array. El c�digo de debajo es un recorte fuera de la misma rutina: mov eax, [ebp-164h] mov edx, ds:00423194[eax*4] En el c�digo anterior, [EBP-164h] contiene un �ndice para una variable global array en offset ds:00423194h. El tipo del array es un entero 32-bit. Eso es por qu� el contador es incrementado por cuatro bytes (el c�digo [eax*4]). Si el array es de un tipo CHAR (1 byte), el c�digo se parecer�: mov eax, [ebp-164h] mov edx, ds:00423194[eax] Si el �ndice y el array son variables locales, el c�digo no podr�a estar tan claro como el c�digo anterior. Un array de tipo DWORD probablemente se desensamblar� como: mov eax, [ebp-164h] mov edx, [ebp+eax*4-40h] En este c�digo, podemos encontrar un array de tipo DWORD comenzando en [EBP-40h]. Supongo que esto ser� bastante por ahora. Hay muchas maneras, que un compilador puede generar c�digo en ensamblador. No hay ninguna manera de escribir todo lo que encontr�. Sigue practicando. Es la �nica manera segura. El desensamblado es impreciso. No siempre debes esperar reconocer todas las variables que utiliza una funci�n. Aunque, prefiero el listado muerto al acercamiento vivo, siempre debemos recordar,que el desensamblado es simplemente otra opci�n que podemos usar. No tiene ning�n sentido esperar por el desensamblador para desensamblar un programa de 3 MB, si podemos encontrar la informaci�n en segundos usando un debugger. Debes estar seguro de la raz�n por la que desmontas este programa en el primer lugar. Es demasiado f�cil ser enga�ado por una funci�n que no es importante para nosotros. Simplemente como eres atra�do por una funci�n vista dentro de tu debugger. En lugar de pegar con tu breakpoint, en cambio examinas la funci�n. �S�lo para averiguar que es una llamada a GetLastError (recuerdas todav�a esa experiencia? ;). |
The cRACKER's n0tES esta
dividido dentro de 12 partes principales: |
|
The
cRACKER's n0TES are Copyright 1998-2000 by TORN@DO of ID.
Todo los Derechos Reservados.
Traducido por Revisado por
X-Grimator.