cRACKER's n0TES

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

SS

Apunta al segmento de la pila del programa.

(E)SP

Indicador de la pila. Apunta al valor actual de la pila. Impl�citamente cambiado por push, pop, call y ret.

(E)BP

Puntero base. Normalmente apunta al marco de pila actual para un procedimiento. Un compilador perfeccionado a veces puede usar (E)BP como un registro universal.

Instrucciones de 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.


Identificando Funciones

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.

La Llamada ( CALL) y La instrucci�n RET

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.

La funci�n Pr�logo y Ep�logo

El pr�logo normal generado por un compilador, ser� alguna variaci�n de �stos:

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.

Funci�n llamada por MFC macro de trazado del mensaje
(Funci�n called by MFC message maps macro)

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:


....
0045C738 db 3\

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:


....
0045C730 dd 111h
0045C734 dd 0
0045C738 dd 57603
0045C73C dd 57603
0045C740 dd 0Ch
0045C744 dd offset loc_423280
....

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 Valor

No 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�n

Antes 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.

Convenio de llamada

Paso del argumento

Mantenimiento de la pila

Distintivo del nombre (C s�lo)

Notas

__pascal

Izquierda a derecha.

La funci�n llamada quita sus propios argumentos de la pila.

M�ndame un e-mail

Usado para casi toda funci�n de exportaci�n en Windows 16-bit.

__cdecl
(convenio de llamada en C)

Derecha a Izquierda.

La funci�n llamada quita sus propios argumentos de la pila.

Prefijo Subrayar para el nombre de la funci�n. Ex: _Foo.

Usado en CRT (librer�a runtime de C).

__stdcall

Derecha a Izquierda.

La funci�n llamada quita sus propios argumentos de la pila.

Prefijo Subrayar para el nombre de la funci�n, @ a�adi� seguido por el n�mero de bytes decimales en la lista del argumento. Ex: _Foo@10.

Usado para casi toda funci�n de exportaci�n en Win32.

__fastcall

Primero se pasan dos argumentos DWORD en ECX y EDX, el resto se pasa de derecha a izquierda.

La funci�n llamada quita sus propios argumentos de la pila.

Un @ es prefijado al nombre, @ a�adido seguido por el n�mero de bytes decimales en la lista del argumento. Ex: @Foo@10.

Debido a que usa un registro espec�fico, s�lo se aplica a los CPUs de Intel. Esto es el convenio de llamada predefinido para los compiladores de Borland (incl. Delphi).

thiscall

este puntero pone en ECX, los argumentos pasados de derecha a izquierda.

La funci�n llamada quita sus propios argumentos de la pila.

Ninguno.

Usado autom�ticamente por c�digo de C++.

naked

Derecha a Izquierda.

La funci�n llamada quita sus propios argumentos de la pila.

Ninguno.

S�lo usado por VxDs.

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:

Contenido de la pila

Situaci�n

Descripci�n

lParam

[EBP+14h]

Empujado por el llamador

wParam

[EBP+10h]

Empujado por el llamador

msg

[EBP+0Ch]

Empujado por el llamador

hDlg

[EBP+08h]

Empujado por el llamador

return EIP

[EBP+04h]

Empujado por instrucci�n CALL

EBP Previo

[EBP+00h]

Empujado por el c�digo prologo

     

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):

Cuando una funci�n est� usando un marco de pila (E)BP, el argumento de funci�n tendr� un offset positivo desde (E)BP

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 Variables

Variables locales

Las 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:

Cuando una funci�n est� usando un marco de pila (E)BP, la funci�n de las variables locales tendr� un offset negativo desde (E)BP

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.
(*n.del t.: Desprotegida)

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 Globales

Determinar 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 Variables

Ya 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:

 TX. Notas del Traductor
 00. INDICE
 01. Ensamblador para Cracker (CoRN2)
 02. SoftICE (Men� de arranque , Configuraci�n, Comandos)
       
 1 Men� de arranque
       
 2 Configuraci�n
       
 3 Comandos
 03. Breakpoints & Detalles de API de Windows
       
 1 Programas restringidos
       
 2 Cajas de di�logo
       
 3 Verificando el Tipo de unidad
       
 4 Acceso a archivos
       
 5 Acceso al Registro
       
 6 Cogiendo n�meros de serie
       
 7 Accediendo a Tiempo & Fecha
       
 8 Generando ventanas
 04. Instrucciones de salto
 05. Instrucciones SET
 06. Tips & Trucos para Crackear
       
 1 Programas restringidos
       
 2 Dongles
       
 3 General
       
 4 Configuraci�n de InstallSHIELD
       
 5 Protecciones con Archivo llave
       
 6 Pantallas molestas
       
 7 L�mites de Runtime
       
 8 Serials
       
 9 Limites de Tiempo
       
10 Programas Visual BASIC
 07. Ventanas de Mensajes Para los Cracker
 08. Identificando funciones, Argumentos, y Variables (Rhayader)
 09. Los Sistemas de Protecciones de comerciales
       
 1 Armadillo
       
 2 C-Dilla SafeDISC
       
 3 SalesAgent
       
 4 SecuROM
       
 5 softSENTRY
       
 6 TimeLOCK
       
 7 VBox
 10. Bitmanipulation (Cruehead)
 11. Teor�a general de Cracking
 12. FAQ


 +A. C�mo contactar conmigo
 +B. �Que es lo Nuevo?


 



The cRACKER's n0TES are Copyright 1998-2000 by TORN@DO of ID.
Todo los Derechos Reservados.
Traducido por
Revisado por X-Grimator.