c y Reversing

278
C Y REVERSING (parte 1) Realmente estoy empezando a escribir un tute al que pomposamente llame C Y REVERSING parte 1 y para los que lo están leyendo, y tratando de ver de que va, les aclaro que la verdad no tengo la menor idea de si esto va a ser un tute solo, o varios, y una muy vaga idea de lo que voy a hacer. Quizas sea que como estoy de vacaciones en el fondo extraño un poco la acción, por eso hay que ver si cuando vuelva al duro trajín diario, tendré aun fuerzas para seguir con esta serie, por eso, preferí escribir esta parte sin obligarme a nada, si sale bien, bien, si gusta mejor, si tengo mas ganas agregare mas, sino la dejare como parte 1 única, como algo básico para profundizar algún día. La idea es Reversear desde cero, primero código fuente en C, lo mas sencillo posible que nosotros mismos haremos o sacaremos de Internet, y lo trataremos de Reversear en IDA, para encontrar en el código, nuestras variables, estructuras, funciones, etc, e ir complicando, para hacernos fuertes en reversing, así cuando tengamos programas que Reversear sin el código fuente como ocurre en la realidad cotidiana, tendremos una guiá de como hacerlo. Como siempre en todos mis tutes les pido disculpas por mi total mal uso de vocabulario técnico, de cualquier manera trato de explicar en forma sencilla, y a pesar de algún error en el vocabulario técnico, lo mostrado es tan simple que igual se entiende, para el que sabe y le encante pavonearse con palabras técnicas difíciles, discúlpeme, yo se hacer las cosas que explico, las hago diariamente, y para ello no necesito saber todos los nombres técnicos, las hago y listo, aunque para explicar en un tute a veces eso trae alguna imprecisiones y posiblemente algún purista iluminado se podrá reír de mi uso de las mismas.(como siempre me importa un pito jeje, mientras los que se inician me entiendan esta todo bien) Las herramientas que utilizaremos serán el IDA 5.5 mas HexRays que esta en mi web: http://ricardonarvaja.net/WEB/OTROS/HERRAMIENTAS%20PRIVADAS/F-G-H-I-J- K/IDA.Pro.Advanced.v5.5.incl.Hex.Rays.Decompiler.v1.1-iND.7z Por supuesto el plugin HexRays verifica la licencia conectándose a Internet, así que ademas de quitar la tilde CHECK FOR UPDATES que aparece en el splash screen del plugin cuando abrimos un ejecutable en el IDA, conviene trabajar en una maquina virtual, de modo de hacer un snapshot, para que si algún chequeo hace que se venza, podamos volver al snapshot anterior y vuelva a funcionar. Utilizaremos también el compilador de C y C++ llamado DEVC++ que aunque es un poquito antiguo, es fácil de usar, el que quiere podrá reemplazarlo por otro, no hay problema, si quieren bajarse el DEVC++ en mi web esta aquí: http://ricardonarvaja.net/WEB/OTROS/HERRAMIENTAS/A-B-C-D-E/devcpp-4.9.9.2_setup.exe Bueno fuera de esto usaremos algunas pocas veces Ollydbg 1.10, algún editor de texto como el Notepad ++ y no mucho mas según mis cálculos. Para que no digan que robamos, lo cual es cierto jeje, la mayor parte de los códigos fuente los sacaremos del curso de C que se encuentra aquí: http://www.nachocabanes.com/c/curso/

description

Curso de programación en C.

Transcript of c y Reversing

Page 1: c y Reversing

C Y REVERSING (parte 1)

Realmente estoy empezando a escribir un tute al que pomposamente llame C Y REVERSING parte

1 y para los que lo están leyendo, y tratando de ver de que va, les aclaro que la verdad no tengo la

menor idea de si esto va a ser un tute solo, o varios, y una muy vaga idea de lo que voy a hacer.

Quizas sea que como estoy de vacaciones en el fondo extraño un poco la acción, por eso hay que

ver si cuando vuelva al duro trajín diario, tendré aun fuerzas para seguir con esta serie, por eso,

preferí escribir esta parte sin obligarme a nada, si sale bien, bien, si gusta mejor, si tengo mas ganas

agregare mas, sino la dejare como parte 1 única, como algo básico para profundizar algún día.

La idea es Reversear desde cero, primero código fuente en C, lo mas sencillo posible que nosotros

mismos haremos o sacaremos de Internet, y lo trataremos de Reversear en IDA, para encontrar en el

código, nuestras variables, estructuras, funciones, etc, e ir complicando, para hacernos fuertes en

reversing, así cuando tengamos programas que Reversear sin el código fuente como ocurre en la

realidad cotidiana, tendremos una guiá de como hacerlo.

Como siempre en todos mis tutes les pido disculpas por mi total mal uso de vocabulario técnico, de

cualquier manera trato de explicar en forma sencilla, y a pesar de algún error en el vocabulario

técnico, lo mostrado es tan simple que igual se entiende, para el que sabe y le encante pavonearse

con palabras técnicas difíciles, discúlpeme, yo se hacer las cosas que explico, las hago diariamente,

y para ello no necesito saber todos los nombres técnicos, las hago y listo, aunque para explicar en

un tute a veces eso trae alguna imprecisiones y posiblemente algún purista iluminado se podrá reír

de mi uso de las mismas.(como siempre me importa un pito jeje, mientras los que se inician me

entiendan esta todo bien)

Las herramientas que utilizaremos serán el IDA 5.5 mas HexRays que esta en mi web:

http://ricardonarvaja.net/WEB/OTROS/HERRAMIENTAS%20PRIVADAS/F-G-H-I-J-

K/IDA.Pro.Advanced.v5.5.incl.Hex.Rays.Decompiler.v1.1-iND.7z

Por supuesto el plugin HexRays verifica la licencia conectándose a Internet, así que ademas de

quitar la tilde CHECK FOR UPDATES que aparece en el splash screen del plugin cuando abrimos

un ejecutable en el IDA, conviene trabajar en una maquina virtual, de modo de hacer un snapshot,

para que si algún chequeo hace que se venza, podamos volver al snapshot anterior y vuelva a

funcionar.

Utilizaremos también el compilador de C y C++ llamado DEVC++ que aunque es un poquito

antiguo, es fácil de usar, el que quiere podrá reemplazarlo por otro, no hay problema, si quieren

bajarse el DEVC++ en mi web esta aquí:

http://ricardonarvaja.net/WEB/OTROS/HERRAMIENTAS/A-B-C-D-E/devcpp-4.9.9.2_setup.exe

Bueno fuera de esto usaremos algunas pocas veces Ollydbg 1.10, algún editor de texto como el

Notepad ++ y no mucho mas según mis cálculos.

Para que no digan que robamos, lo cual es cierto jeje, la mayor parte de los códigos fuente los

sacaremos del curso de C que se encuentra aquí:

http://www.nachocabanes.com/c/curso/

Page 2: c y Reversing

si alguien tiene ganas de complementar leyéndolo a la misma vez que vamos aquí reverseando los

ejemplos pues adelante, es una buena lectura complementaria ya que aquí no haremos un curso de C

sino que usaremos los ejemplos para reversear.

Como siempre comenzaremos con el ejemplo mas sencillo del universo.

#include <stdio.h>

main()

{

printf("Hola");

}

Para los que no conocen C estas tres lineas que quizás sean el mínimo programa, se explican

sencillamente de la siguiente manera.

#include <stdio.h>

Esta linea se agrega para ampliar el lenguaje básico de C, que no trae incluida en el mismo, la

función que permite imprimir en pantalla (printf) es como si fuera un import en python para los que

conocen el mismo, sin este include no podríamos imprimir la salida en este caso el texto “Hola”

main()

{

}

La función main es la función principal del programa y dentro de las llaves que están a

continuación escribiremos el código que queremos ejecutar, en este caso el printf para sacar por

pantalla el texto “Hola Mundo”

Esta es una explicación muy básica, como siempre el que quiera la explicación completa, puede leer

la del curso de Cabanes en esta pagina.

http://www.nachocabanes.com/c/curso/cc01.php

Bueno abrimos el DEVC++ vamos a FILE-NEW-SOURCE FILE y ahí nos abrirá un espacio en

blanco para tipear o copiar y pegar nuestro código fuente.

Page 3: c y Reversing

Luego yendo a FILE-SAVE AS lo guardamos fijándonos en cambiar para que lo guarde como

código fuente en C.

Ahora lo compilamos yendo a EXECUTE-COMPILE

Si les aparece algún error, recuerden que en C cada orden debe terminar con punto y coma, en este

caso la orden que le damos para que imprima en pantalla a través de la función printf debe tener un

punto y coma al final, si lo quitamos nos dará un error medio critico como vemos en la imagen de

abajo, así que cuidado con ello.

También el DEVC++ da error si tienen instalado en la misma maquina el MINGW, así que

desinstalen temporalmente el MINGW y todo funcionara normalmente.

Page 4: c y Reversing

Error por falta del punto y coma

Así que ya tenemos nuestro ejecutable, será creado en la misma carpeta que tenemos el archivo

fuente .c, con el mismo nombre pero con extensión .exe.

Corramos el ejecutable dentro de una consola a ver si va bien.

Page 5: c y Reversing

En mi caso como puse un espacio en el nombre tuve que ejecutarlo al mismo entre comillas sino no

lo tomara, vemos el hola impreso en la consola así que funciono, ahora abramos este ejecutable en

IDA.

En este caso como nuestro main es una función sin argumentos y sin variables, al menos nosotros

no creamos ninguna, debemos separar las cosas que le agrego el compilador, para entender el

código original, en este caso al verlo en IDA, que se abre directamente en el main y evita mostrar el

código desde el entry point, que por supuesto no es creado por nosotros y es puramente creado por

el compilador para hacer los includes necesarios y poder crear un contexto adecuado para que

nuestro código corra bien.

Aquí vemos que el compilador modifico nuestro main haciéndolo una función de tres argumentos

que no son nuestros, sino del compilador, y tiene una variable. var_4 que creo el compilador la

cual si hacemos las cuentas devolverá siempre 10 y la utilizara el compilador al cerrar el programa,

por supuesto nada de esto es código prpio, vayamos a nuestro código, propiamente dicho.

.text:004012BA mov dword ptr [esp], offset aHola ; "hola"

.text:004012C1 call printf

Allí vemos que pone en el stack un puntero a la string hola que esta en la sección rdata, si hacemos

click en aHola, nos muestra que esta exactamente en 403000 y que es el argumento de la funcion

Page 6: c y Reversing

printf.

Si vamos a DEBUGGER y elegimos por ejemplo el debugger local de win32

y ponemos breakpoint alli.

Page 7: c y Reversing

Y damos START PROCESS, en la ventana del stack cuando se detiene cambiamos a JMP TO ESP

para que muestre el código que apunta ESP, o sea el stack y no cualquier cosa como normalmente

hace , jeje.

.

Vemos el puntero a la string hola, que podemos verla en el hex dump, haciendo click derecho –

FOLLOW IN HEX DUMP.

Y vemos que allí esta.

Es importante que cuando reverseamos separemos la paja del trigo y no nos metamos con el código

que agrega el compilador, ya vimos que lo anterior al main es creado por el mismo y dentro del

mismo main solo dos lineas pertenecen a nuestro código.

Una forma de separar y comprender que agrega de mas el compilador, es variar un poco el código

fuente a este:

# include <stdio.h>

main(){

funcion1();

Page 8: c y Reversing

}

funcion1(){

printf("hola");

}

Como vimos que al main, el compilador le agrega argumentos y variables que usa el mismo, dentro

de main creamos una funcion nuestra llamada funcion1, y dentro de ella si ponemos el printf, si la

compilamos y corremos vemos que funciona igual que la anterior pero al verla en el IDA.

Aquí vemos el main que es similar al anterior y dentro el llamado a nuestra función, que como

vemos en este caso no se le pasa argumento ninguno, renombremosla a funcion1 como en nuestro

código, entrando a ella y click derecho en el nombre, rename o edit funcion y le cambiamos a

funcion1.

Page 9: c y Reversing

Allí le cambiamos el nombre.

Quedara así

Page 10: c y Reversing

Vemos que el único cambio que hizo es ponerle el int delante, el resto vemos mucho mas limpio el

código y lo que es estrictamente nuestro, no declaramos variables y aquí no hay, no hay argumentos

pasados a la funcion tan cual nuestro código, vemos el puntero a hola, y el llamado a printf.

Si retrocedemos al main vemos que ahora nuestra función aquí también figura como funcion1.

Este ejemplo no aporta mucho para el reversing pero es importante que uno con la experiencia sepa

no darle importancia a lo que el compilador agrega al código fuente, de forma de poder interpretar

solo las lineas del código original del autor y no fruta, ademas normalmente los programas

contienen cientos de funciones dentro del main y es muy raro tener que reversear el main,

normalmente estaremos reverseando dichas funciones, pero es bueno ver la diferencia y saber que

en el main el compilador mete mucha mas fruta y ver cual es.

A partir de ahora todos los ejemplos que en el curso de C están escribiendo código en el main,

nosotros los haremos en una función dentro del main para poder reversear mejor y no confundir con

tanta fruta.

Para terminar el ejemplo 1 le agregamos la función getchar() que queda esperando hasta que se

aprete ENTER en la consola, para que no se cierre la misma y poder arrancar el ejecutable dando

Page 11: c y Reversing

doble click.

# include <stdio.h>

main(){

funcion1();

}

funcion1(){

printf("hola");

getchar();

}

Vemos que al compilarlo nuevamente no hay ninguna diferencia, solo el llamado a getchar para que

no se cierre al ejecutarlo fuera de la consola.

Si le damos ENTER se cerrara.

Veamos el siguiente ejemplo que sumara dos números y reverseemoslo.

# include <stdio.h>

main(){

funcion1();

}

Page 12: c y Reversing

funcion1(){

int primerNumero;

int segundoNumero;

int suma;

primerNumero = 234;

segundoNumero = 567;

suma = primerNumero + segundoNumero;

printf("Su suma es %d", suma);

getchar();

}

Vemos la declaración de tres variables enteras o int, una llamada primerNumero, otra

segundoNumero y otra suma, las dos primeras las inicializo con los valores 234 y 567 y la variable

suma quedara sin inicializar, y guardara el resultado de la misma.

printf("Su suma es %d", suma);

La instrucción printf solo imprime strings, pero acepta mediante format string reemplazar en este

caso el %d por un la string del numero que se guardo en suma, de esta forma como en suma quedara

el numero 801, y mediante format string se convertirá en la string “801” y reemplazara al %d

quedando el mensaje de salida

Su suma es 801

Veamos si funciona primero, compilemos y ejecutemoslo con doble click ya que pusimos el

getchar() al final.

Bueno funciona veamoslo ahora en IDA.

Page 13: c y Reversing

Ese es el main ahora entremos en nuestra función.

Primero renombraremos la función tal como hicimos la vez anterior usando rename y ya queda con

nuestro nombre, lo mismo haremos con las variables locales, en este caso nosotros le ponemos el

mismo nombre que el código fuente, pues lo hicimos nosotros y los tenemos, pero si fuera una

funcion de un programa que no tenemos el código, habrá que renombrar las variables con un

Page 14: c y Reversing

nombre aproximado a lo que interpretemos que hace cada una en el código.

Vemos que var_4 se la inicializa con el valor hexa EA que en decimal es 234 o sea que sera

primerNumero, la renombramos, al igual que a segundoNumero y a suma.

Allí vemos nuestras variables locales, como la funcion no tiene argumentos, las variables solo

tienen sentido localmente así que no es necesario propagarlas como en el caso de renombrar

argumentos que veremos mas adelante.

El código se interpreta fácilmente

mov [ebp+primerNumero], 0EAh

mov [ebp+segundoNumero], 237h

mov eax, [ebp+segundoNumero]

add eax, [ebp+primerNumero]

mov [ebp+suma], eax

Luego de inicializar ambas variables se mueve a EAX el valor de segundoNumero y se le suma el

valor de primerNumero quedando el resultado en EAX, el cual se guarda en la variable suma.

mov eax, [ebp+suma]

mov [esp+4], eax

mov dword ptr [esp], offset aSuSumaEsD ; "Su suma es %d"

call printf

Vemos que aun no teniendo el código fuente, viendo que las variables guardan miembros de una

suma, y una tercera guarda el resultado, pues ya tenemos la posibilidad de ponerles nombres

adecuados a la funcion de cada una.

A continuación se mueve a EAX ese valor resultado de la suma, el cual se guarda en el stack para

pasarlo como argumento a printf, el otro argumento es un puntero a la string "Su suma es %d"que

Page 15: c y Reversing

se envía también al stack ya que en este caso printf recibe dos argumentos.

Si ponemos en el IDA un breakpoint en printf y acomodamos el stack con JMP TO ESP cuando se

detiene vemos en el primer lugar el puntero a la string Su suma es ... , y en segundo lugar el

segundo argumento 321 hexa que es el entero resultado de la suma, que la funcion printf convertirá

a string y lo agregara el resto y mostrara en la consola como salida.

Si ponemos un BP apenas entrado en nuestra funcion.

Lógicamente como acabamos de detenernos al entrar en un call, lo primero que habrá en el stack,

sera el return address donde volverá al terminar de ejecutarse mi funcion, así que si en el mismo

hago JMP TO ESP y veo.

Si hago click derecho FOLLOW IN DISASSEMBLER veré donde volverá al retornar de mi

funcion.

Page 16: c y Reversing

Por supuesto volveré a la funcion main ya que mi funcion esta dentro de ella, debajo del return

address si mi funcion tuviera argumentos que se pasan con PUSH o se copian al stack antes de

entrar a la misma, lógicamente estarían debajo del return address, pues se pushean antes de entrar,

en este caso al no tener argumentos no veremos nada, aquí debajo marcamos la zona de argumentos

que no esta usada en este caso.

Justo arriba del return address encontraremos dependiendo del tipo de funcion, si lo primero que

hace al entrar en la misma es PUSH EBP, pues allí estará guardado el ebp del main o de la funcion

que llamo a la nuestra, y al salir lo recuperará con POP EBP, o LEAVE.

Normalmente después del primer PUSH EBP, reservara lugar para las variables en el stack.

.text:004012C1 push ebp

.text:004012C2 mov ebp, esp

.text:004012C4 sub esp, 18h

Iguala EBP con ESP y luego le resta a ESP un valor que depende de la cantidad de variables y lugar

necesario que el compilador decida que necesita, en este caso le resta 24 decimal o sea que desde la

dirección del stack donde pusheo el valor de EBP, hacia arriba quedara lugar para las 3 variables

dword y un poco mas de espacio.

Page 17: c y Reversing

Ahora al hacer JMP to ESP ya que ESP cambio, queda a la vista el espacio en rosado reservado, y

justo debajo en celeste el EBP guardado y el return address que habíamos visto.

En ese lugar en rosado, debe guardar los valores con los que inicializa las variables si vamos

traceando al pasar por.

.text:004012C7 mov [ebp+primerNumero], 0EAh

.text:004012CE mov [ebp+segundoNumero], 237h

Vemos que inicializo las dos variables y el resultado lo guardara en la variable suma que toma su

valor cuando pasamos por aquí.

004012DB mov [ebp+suma], eax

Allí vemos como guarda el 321 hexa, resultado de la suma, justo arriba de las dos variables

anteriores.

Page 18: c y Reversing

Resumiendo vemos en el stack que quedaron bien marcadas las 3 zonas, la amarilla de argumentos

no usada en este caso, la celeste con el EBP guardado y el return address y la rosa para las 3

variables locales y de ahí hacia arriba habrá lugar para lo que necesite, por ejemplo para pasar

argumentos a printf.

Vemos que lo que en el IDA descubrimos debuggeando, si paramos el debugger y miramos el

listado muerto y hacemos doble click en cualquiera de las variables de nuestra funcion, IDA nos

muestra lo mismo, sin tener que debuggear.

Como habíamos visto el return address aquí llamado r es el limite, abajo del mismo esta la zona

amarilla de argumentos (en este caso no hay argumentos), arriba del return address esta el EBP

guardado (aquí llamado s por stored ebp) y arriba la zona rosada para las variables, allí están las

tres que creamos, primerNumero, segundoNumero y suma, mas arriba se utilizara en el resto de

nuestra funcion para pasar argumentos, en esta caso a printf.

Si queremos ver como decompila nuestra funcion el HexRays en pseudocode, vamos a VIEW-

OPEN SUBVIEWS-PSEUDOCODE.

int __cdecl funcion1()

{

printf("Su suma es %d", 801);

return getchar();

}

Vemos que interpreta la funcion (ya que la misma no tiene argumentos ni variables dado que los

Page 19: c y Reversing

valores que suma son constantes siempre), como una funcion que imprime el mensaje Su suma es

801 y nada mas.

En el caso que quisiéramos ver como decompila el código completo, hay que ir a FILE-PRODUCE

FILE-CREATE C FILE y tratara de hacer algo mas o menos aproximado a nuestro código aunque

no es exacto ni mucho menos.

En C las variables se pueden inicializar al mismo momento que se crean en ese caso el código

quedaría así

# include <stdio.h>

main(){

funcion1();

}

funcion1(){

int primerNumero = 234;

int segundoNumero = 567;

int suma;

suma = primerNumero + segundoNumero;

printf("Su suma es %d", suma);

getchar();

}

Si compilan este y lo abren en IDA y entran a nuestra funcion verán que es exactamente igual al

anterior y no cambio nada.

El siguiente ejemplo usa la funcion scanf, para ingresar una string por teclado, y tal cual printf,

permite format string, por lo cual si el primer argumento es %d, transformara la string tipeada en un

numero, a la inversa de printf, eso si debemos colocar el & delante del nombre de la variable en

scanf ya explicaremos mas adelante porque.

# include <stdio.h>

main(){

funcionSuma();

}

funcionSuma(){

int primerNumero, segundoNumero, suma; /* Nuestras variables */

printf("Introduce el primer numero ");

scanf("%d", &primerNumero);

printf("Introduce el segundo numero ");

scanf("%d", &segundoNumero);

suma = primerNumero + segundoNumero;

printf("Su suma es %d", suma);

getchar();

}

Page 20: c y Reversing

Vemos que luego de declarar las tres variables como int, imprime el mensaje Introduce el primer

numero, allí el usuario tipeara una string que debe representar un numero sino dará error, luego lo

mismo para el segundo numero, realizara la suma la cual guardara en la variable suma, e imprimirá

la salida mediante printf, compilemos este código y veamos nuestra funcion en IDA.

Allí estamos en el main y vemos nuestra funcion, entramos y la renombramos, lo mismo que las

variables como hicimos en el ejemplo anterior.

Quedara así:

Page 21: c y Reversing

Si hacemos click en cualquiera de las variables vemos que la disposición de las misma no cambio

nada con respecto al caso anterior.

Todo lo explicado para el caso anterior aquí se cumple, tenemos el mismo r o return address, arriba

el stored ebp y arriba las tres variables, por supuesto aquí tampoco tenemos argumentos así que la

parte amarilla no la usamos.

lea eax, [ebp+primerNumero]

mov [esp+4], eax

mov dword ptr [esp], offset aD ; "%d"

call scanf

Vemos aquí como se aclara el sentido del & que tuvimos que colocar delante de los nombres de las

Page 22: c y Reversing

variables en el caso de scanf, el símbolo & significa la dirección de dicha variable en el stack no el

valor de la misma, obviamente la variable aun no tiene valor es solo un lugar a llenar en el stack,

por eso mediante el & que equivale al LEA lo que hacemos es darle la dirección en el stack donde

dicha variable esta, para que scanf escriba allí y la llene con el valor que tipeamos.

En el primer ejemplo directamente inicializamos el valor de la variable con

mov [ebp+segundoNumero], 237h

pero esta variable es una variable local de mi funcion, solo tiene sentido dentro de la misma y no

tiene sentido dentro de scanf, de esta forma al hacer el LEA obtengo la dirección en el stack de la

variable

lea eax, [ebp+primerNumero]

Luego si se le pasa esa dirección en el stack que esta en EAX como argumento de scanf como en

nuestro ejemplo

En cualquier momento que dentro de scanf que se haga por ejemplo

mov [eax],678h

como EAX tiene la dirección de la variable en el stack, al escribir en el contenido de EAX en

realidad estamos inicializando la variable primerNumero y llenando su contenido.

Si lo debuggeamos lo veremos mas claro.

Al correr el debugger parara allí.

Page 23: c y Reversing

Al cambiar con JMP TO ESP vemos en 22ff5c el return address, en 22ff58 el stored EBP, y justo

arriba deben ubicarse las tres variables que tienen aun cualquier valor pues no han sido inicializadas

La variable primer numero se encuentra en 22ff54 y vale 4000 que es la fruta que había ah en el

stack, de esta forma al hacer el LEA, queda en EAX la dirección 22ff54 de la variable, pues eso es

lo que me interesa pasarle a scanf, para que escriba allí y no el 4000 que es basura que hubiera

quedado en EAX de haber hecho mov en vez de lea.

Page 24: c y Reversing

De esta forma se ve claro que al pasarle el la dirección dela variable en EAX, en cualquier momento

que se haga

mov [EAX], XXX

sera lo mismo que hacer

mov [22ff54],XXX

Lo que guardara el valor XXX en dicha variable remplazando el 4000 que era basura y se

inicializara.

Luego si seguimos traceando vemos que en la consola nos pedirá que tipeemos un numero, en mi

caso tipee 788 y ENTER.

Al volver veo que guardo en hexa dicho valor en la variable primerNumero.

Luego lo mismo guarda el segundoNumero justo arriba y la suma en la tercera variable, luego

Page 25: c y Reversing

imprime por consola la salida, lo que no funciona en este caso es el bloqueo mediante getchar, debe

quedar como que mantenemos apretado ENTER y continua y se cierra.

main(){

funcionSuma();

getchar();

}

funcionSuma(){

int primerNumero, segundoNumero, suma; /* Nuestras variables */

printf("Introduce el primer numero ");

scanf("%d", &primerNumero);

printf("Introduce el segundo numero ");

scanf("%d", &segundoNumero);

suma = primerNumero + segundoNumero;

printf("Su suma es %d", suma);

getchar();

}

Bueno solucionamos el problema agregando otro getchar el cual si detiene el programa hasta que

apretamos ENTER nuevamente.

Vemos que en este caso el pseudocode creado por el HexRays no puede evitar la creación de 2

variables ya que allí se debe guardar lo que tipea el usuario.

int __cdecl sub_4012C6()

{

int v1; // [sp+10h] [bp-8h]@1

int v2; // [sp+14h] [bp-4h]@1

printf("Introduce el primer numero ");

scanf("%d", &v2);

printf("Introduce el segundo numero ");

scanf("%d", &v1);

printf("Su suma es %d", v2 + v1);

return getchar();

}

Crea dos variables llamadas v1 y v2 y en el scanf le pone el & delante para obtener la dirección de

las mismas en el stack, es casi el mismo código que el nuestro salvo que usa una variable menos

pues no guarda el resultado de la suma.

Bueno la verdad que me canse y no se si a alguien le interesa profundizar en reversing de códigos

cada vez mas complejos, pero bueno sino quedara esta parte 1 como parte única para el que le sirva,

veremos también las ganas que tenemos de seguir una parte 2 jejeje.

Un saludo a todos los Crackslatinos

Ricardo Narvaja

Page 26: c y Reversing

C Y REVERSING (parte 2) por Ricnar

Bueno ahora lo que sigue es ver un poco el tamaño de las variables que declaramos, el que tiene

alguna duda de los sistemas numéricos decimal, hexadecimal y octal no lo repasaremos aquí, puede

leer la parte dos del curso de Cabanes donde esta todo bien explicado y sencillo.

http://www.nachocabanes.com/c/curso/cc02.php

Lo que hemos visto al reversear es que al crear una variable como int, el sistema le asigna un lugar

en la memoria, que es un dword o sea 4 bytes, ademas si no especificamos nada y solo declaramos

int, este valor sera tomado como signed int, o sea que se considerara con signo, que en hexa

significa que desde 00000000 hasta 7fffffff se consideraran números positivos y a partir de

80000000 hasta ffffffff se consideraran los negativos siendo el -1 igual a ffffffff, el -2 sera igual a

fffffffe y así hasta el máximo negativo que sera el 80000000.

Vemos que de esta forma usando int o signed int el máximo numero positivo es 7fffffff, ahora si

uno sabe que no va a utilizar números negativos y necesita mas rango puede declarar una variable

como unsigned int y de esta forma siempre sera positiva desde 0 hasta ffffffff ampliando el rango y

eliminando la posibilidad de los negativos.

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

int primerNumero;

signed int segundoNumero;

unsigned int tercerNumero;

primerNumero = -1;

segundoNumero = -2;

tercerNumero = 2147483650;

printf("El primer numero es %d, ", primerNumero);

printf("el segundo es %d, ", segundoNumero);

printf("el tercer numero es %d.", tercerNumero);

}

Si compilamos y ejecutamos esto vemos que el valor unsigned es mostrado como signed, pero eso

no es porque este mal declarado ya lo veremos sino porque el format string con %d solo convierte

signed int, para imprimir unsigned int debemos usar %u, pero compilemos y miremos un poco

primero este ejemplo.

Page 27: c y Reversing

Vemos que la tercera variable la imprimió tal cual fuera signed, ya que al pasarse del máximo valor

positivo que habíamos visto que era 2147483647, por lo tanto 2147483650 corresponde al hexa

80000002 o sea a uno de los negativos mas altos pues el máximo negativo es 80000000h.

Veamos nuestro engendro en IDA

Allí vemos nuestras tres variables que ocupan un dword cada uno al ser las tres declaradas como int,

sea signed o unsigned.

mov [ebp+var_C], 80000002h

Allí vemos que la variable unsigned le asigna el valor 80000002 como hemos visto.

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

signed int primerNumero;

Page 28: c y Reversing

signed int segundoNumero;

unsigned int tercerNumero;

unsigned int suma;

primerNumero = -1;

segundoNumero = -2;

tercerNumero = 2147483650;

suma= tercerNumero + 2;

printf("El primer numero es %d, ", primerNumero);

printf("el segundo es %d, ", segundoNumero);

printf("el tercer numero es %u.", suma);

}

Ahora vamos a ver como maneja la suma agregamos una variable suma que sera unsigned y al valor

que había colocado en tercer numero le sumo 2, si fuera negativo, el resultado seria diferente,

veamoslo en IDA, cambiemos también el %d por %u para que imprima los unsigned, y al compilar

y ejecutarlo vemos que la suma fue realizada correctamente y se muestra correctamente ahora.

Vemos que considero el numero como positivo y le sumo 2 y el resultado es correcto si hubiera

sumado dos a un numero negativo el resultado seria obviamente diferente.

main(){

funcion2();

getchar();

}

funcion2(){

char primerNumero;

short segundoNumero;

int tercerNumero;

primerNumero = -1;

segundoNumero = -2;

tercerNumero = 500;

printf("El primer numero es %d \n", primerNumero);

printf("el segundo es %d \n", segundoNumero);

printf("el tercer numero es %d \n", tercerNumero);

}

Page 29: c y Reversing

Si compilamos esto veremos en el IDA que char es el equivalente a byte, que short es el

equivalente a word y que int como ya vimos es el equivalente a dword.

Allí vemos como se acomodaran en el stack las tres variables declaradas, arriba del return address

y el stored ebp y como a cada una el compilador le reserva el espacio justo según el tipo, para la

variable que es un int sera reservado un dword o dd como muestra el IDA hay 4 bytes de espacio

reservado (de 8 a 4), luego el short ocupara un word o dw en IDA (de 4 a 2) luego habrá un byte

sin definir (de 2 a 1) y el char ocupara un byte (de 1 a 0).

También tenemos la posibilidad de manejar floats, para poder imprimirlos se utiliza %f.

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

char primerNumero;

short segundoNumero;

float tercerNumero;

primerNumero = -1;

segundoNumero = -2;

Page 30: c y Reversing

tercerNumero = 500;

printf("El primer numero es %d \n", primerNumero);

printf("el segundo es %d \n", segundoNumero);

printf("el tercer numero es %f \n", tercerNumero);

}

Para manejar floats utilizara las instrucciones de punto flotante que no explicaremos aquí, el que

quiera ver puede consultar en este link, no es nada del otro mundo FLD carga la variable float en el

stack de punto flotante que no es el mismo que el del programa, y luego FSTP guarda en [esp+4] en

el stack del programa ese valor para pasárselo como argumento al printf.

http://homepage.mac.com/eravila/asmix86a.html

Ahora tenemos el siguiente código que usa la funcion sizeof() para ver el tamaño que ocupa cada

variable en la memoria.

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

char primerNumero;

short segundoNumero;

int tercerNumero;

float cuartoNumero;

primerNumero = -1;

segundoNumero = -2;

Page 31: c y Reversing

tercerNumero = 500;

cuartoNumero = 200;

printf("El tamanio del char es %d \n", sizeof(primerNumero));

printf("El tamanio del short es %d \n", sizeof(segundoNumero));

printf("El tamanio del int es %d \n", sizeof(tercerNumero));

printf("El tamanio del float es %d \n",sizeof(cuartoNumero));

}

Al ejecutarlo

Veamoslo en IDA.

Vemos que el tamaño de las variables no se resuelve en tiempo de ejecución del programa, sino que

lo resuelve el compilador y ya compila con el tamaño correcto en cada caso reemplazándolo por la

constante correspondiente.

El tipo de datos CHAR

Vimos que el tipo de datos char ocupa un byte, pero ademas de poder almacenar un numero de un

byte cualquiera como vimos, se usa principalmente para almacenar un carácter.

# include <stdio.h>

main(){

funcion2();

getchar();

}

Page 32: c y Reversing

funcion2(){

char primerNumero;

primerNumero = 68;

printf("El primer numero es %d \n", primerNumero);

printf("El segundo es %c \n", primerNumero);

}

Vemos que guardamos el numero 68 decimal que en la tabla ASCII corresponde a la D, si lo

imprimimos como numero entero mostrara el 68, pero si usamos el format string con %c lo

imprimirá como la letra D al convertirlo a carácter.

Si lo vemos en IDA vemos que es la misma variable la que le pasa al printf en ambos casos, solo

que en un caso al ser %d lo muestra como entero y en el otro al ser %c lo muestra como carácter

según la tabla ASCII.

Vemos que estamos mirando ejemplos sencillos para ver como se manejan las variables e ir

familiarizándonos como se ven en IDA e ir incrementando de a poco la dificultad.

Page 33: c y Reversing

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

char letra1, letra2;

printf("Teclea una letra ");

scanf("%c", &letra1);

letra2 = 'a';

printf("La letra que has tecleado es %c y la prefijada es %c",

letra1, letra2);

getchar();

}

Vemos dos variables definidas como char, la variable llamada letra1 tomara lo que teclea el usuario

usando la funcion scanf, a la cual se le pasa como argumentos %c para que convierta lo tipeado en

carácter, y el segundo argumento como vimos es la dirección de memoria de la variable letra1 con

el & delante, por supuesto lo veremos como LEA en el IDA, como ya explicamos.

Luego se inicializa la variable letra2 con la “a”, y se imprimen ambas, la a y el carácter tipeado por

el usuario.

Lo reversearemos como si no conociéramos el código y renombraremos las variables y la funcion a

nuestro gusto con los nombres que queramos según el uso de cada una.

Page 34: c y Reversing

Como todavía se supone que no se que hace la funcion, le pondré un nombre cualquiera.

Por ahora le puse como nombre funcion investigada, ahora comenzare a ver que hace cada variable,

para ponerle el nombre correspondiente., al marcar la var_1 se resaltan todos los lugares que la

misma se usa en esta funcion, también si es muy grande la funcion, podría apretar la X y me

mostraría en una lista sus referencias.

Page 35: c y Reversing

Obviamente si queremos saber donde se va a inicializar una variable, en el caso que no se inicialice

con una asignación directa por ejemplo mediante un mov dentro de nuestra funcion, en ese caso

debemos ver donde hay un lea, ya que vimos que cuando el compilador usa el lea obtiene la

dirección de memoria en el stack donde esta ubicada dicha variable, y es seguro que a continuación,

lo usara como argumento en alguna funcion o call para llenarla o asignarle un valor dentro de el

mismo. (como vimos en los ejemplos anteriores las variables locales solo se pueden inicializar en

forma directa dentro de nuestra funcion, en cualquier api, o call dentro de nuestra funcion, habrá

que pasarle la dirección de memoria de dicha variable lo que se hace mediante el & y aquí se vera

como un LEA)

Así que aunque la funcion sea larguísima, buscar el lea sobre las referencias de la variable que no

esta inicializada en forma directa, nos llevara al punto donde se inicializara la misma.

lea eax, [ebp+var_1] <-----------------------

mov [esp+4], eax

mov dword ptr [esp], offset aC ; "%c"

call scanf

Así que aunque no tengamos el código fuente y al ver que la dirección de la variable en el stack se

pasa a EAX y de allí se guarda en el stack para usar como argumento de scanf, conociendo la api

scanf, nos damos cuenta que dicha variable guardara un carácter, dado que el otro argumento de

scanf es %c.

Así que ya se para que sirve la variable, para guardar un carácter tipeado por el usuario, así que le

pondré un nombre acorde a su uso.

Page 36: c y Reversing

Y tomara su valor dentro de scanf.

Vemos que hasta ahora no ejecute el código ni debugeé nada y voy sacando conclusiones sobre la

funcion.

Al ver las referencias de la otra variable vemos que no necesita un lea pues se asigna en forma

directa dentro de nuestra funcion con un mov, se le asigna la constante 61 hexa que sabemos que

como la variable es char lo interpretara como la “a” por la tabla ASCII, así que cambiemos eso.

Si hago click derecho en el 61 hexa, veo que IDA me da la posibilidad de cambiar la representación

según el tipo de datos, así que como se que es un char lo cambio a la letra “a” que se ve en el menú

desplegable. Y cambio el nombre de la variable a letra_a.

Page 37: c y Reversing

movsx eax, [ebp+letra_a]

mov [esp+8], eax

movsx eax, [ebp+caracter_tipeado]

mov [esp+4], eax

mov dword ptr [esp], offset aLaLetraQueHasT ; "La letra que has tecleado es %c y la pr"...

call printf

La ultima parte de la funcion manda como argumentos al stack a las dos variables, letra_a y

carácter_tipeado, y ademas también la string donde se realizara el format string usando %c en

ambos casos, con lo cual sabemos que imprimirá la string.

La letra que has tecleado es %c y la prefijada es %c

pero reemplazara los dos %c por las dos variables una tendra la letra a y la otra letra tipeada por el

usuario que si por ejemplo tipeara una d quedaría.

La letra que has tecleado es d y la prefijada es a

Con lo cual ya sabemos que hace la funcion, es una especie de juego para ver si en un solo tiro

acertás la letra prefijada, así que ahora que ya sabemos que hace la funcion le ponemos otro

nombre.

Page 38: c y Reversing

El código fuente que arma el HexRays de nuestra funcion es el siguiente

int __cdecl juego_bastante_tonto()

{

char caracter_tipeado; // [sp+17h] [bp-1h]@1

printf("Teclea una letra ");

scanf("%c", &caracter_tipeado);

printf("La letra que has tecleado es %c y la prefijada es %c", caracter_tipeado, 97);

return getchar();

}

Vemos que como siempre simplifica al máximo y usa una sola variable char en la que guarda el

tipeo del usuario, la otra directamente la suprime y usa una constante 97 decimal, que es 61 en hexa

o sea la letra a, pues como vimos la variable char contiene el valor hexa que luego se transforma en

el printf al valor de la tabla ascII por el %c.

Con lo cual terminamos este sencillo caso de reversing jeje hasta la parte 3 (si hay je)

Saludos Crackslatinos.

Ricardo Narvaja

Page 39: c y Reversing

C Y REVERSING (parte 3) por Ricnar

Continuaremos paso a paso con diferentes códigos en C, agregaremos ahora la comprobación de si

se cumple una condición o if . Dejamos para mas adelante el manejo de cadenas de caracteres, para

seguir el mismo orden que el Curso de C que estamos usando como base para reversear.

Veamos este código:

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

int numero;

printf("Escribe un numero: ");

scanf("%d", &numero);

if (numero>0) printf("El numero es positivo.\n");

getchar();

}

Vemos que luego de crear una variable int y mostrar el mensaje de que imprima un numero usando

printf, usa scanf para que el usuario tipee un numero el cual guarda en la variable pasandole la

dirección de la misma.

A continuación el if, en el paréntesis siguiente al mismo verifica la condición, en este caso si el

numero tipeado es mayor que 0 o sea positivo y si es así va a ejecutar el printf que muestra el texto

“El numero es positivo”, sino continua el programa y va al getchar para luego de que tipeemos

ENTER se cierre.

Veamos como se ve este código sencillo en el IDA, compilemos y abramoslo.

Page 40: c y Reversing

Si yo no conozco el código fuente veo que es una funcion con una sola variable, así que le cambio

el nombre a la funcion, por algo propio, muchos objetan que cambiemos al iniciar el nombre de la

funcion, pero tenemos que entender que muchas veces estaremos reverseando funciones de

programas muy extensos, y el hecho de cambiarle el nombre hará que resalte mas si estamos en otra

parte del programa y vemos que llama a la susodicha funcion, que con el nombre original sera una

mas entre todas las funciones con nombres numericos.

A continuación miraremos la variable, al marcar la misma y apretar X para las referencias vemos

que hay un lea o sea que como ya explicamos allí obtendrá la dirección de la variable para pasársela

como argumento a otra funcion o api que la inicializará, vayamos allí.

Page 41: c y Reversing

lea eax, [ebp+var_4]

mov [esp+4], eax

mov dword ptr [esp], offset aD ; "%d"

call scanf

Como vemos EAX obtendrá mediante el LEA la dirección de dicha variable int, y la misma se

inicializará con el valor que tipee el usuario usando scanf, el mismo será un entero ya que como

argumento de la api se usa %d.

Así que sabemos que la variable int creada sirve para eso, así que pongamosle un nombre acorde a

su uso.

Como dicho int al no aclarar nada es signed, puede ser positivo o negativo, por ello el if que

colocamos en el código el compilador lo transforma en las dos instrucciones siguientes que

contienen la misma condición de verificar si el numero es mayor que 0 y decidir.

Aquí compara si es mayor que 0.

cmp [ebp+numero_tipeado], 0

y en la siguiente linea se tomara la decisión de saltar o no según el resultado de esta comparación,

vemos que el compilador elijio de todos los saltos condicionales posibles el JLE.

jle short loc_4012FD

Los que no conocen de saltos condicionales pueden consultar

http://moisesrbb.tripod.com/unidad5.htm#u5111

Page 42: c y Reversing

Allí verán que el salto JLE toma en cuanta el signo al comparar, por lo cual a todos los efectos

saltara si es menor o igual a cero, y no saltara si es positivo o sea mayor que cero.

Vemos en la imagen que el IDA nos muestra una bifurcación en el flujo del programa, que nos da la

idea de que puede ir por estos dos caminos, que son las flechas, una verde que muestra el camino si

la comparación es cierta o sea es negativo y entonces saltea el printf y va directamente al getchar, y

la roja es el camino si la comprobación es falsa o sea no es negativo, o sea es positivo, con lo cual

no salta y sigue el flujo del programa por el printf donde saca el mensaje de que el numero es

positivo.

Una vez que vemos que la funcion sirve para determinar si el numero tipeado es positivo,

cambiamos el nombre de la misma.

El HexRays es una herramienta que nos sirve como apoyo, veamos como interpreta el código fuente

de nuestra función.

int __cdecl numero_tipeado_es_positivo()

{

Page 43: c y Reversing

int numero_tipeado; // [sp+14h] [bp-4h]@1

printf("Escribe un numero: ");

scanf("%d", &numero_tipeado);

if ( numero_tipeado > 0 )

printf("El numero es positivo.\n");

return getchar();

}

Vemos que en este caso la interpretación es parecida.

Si cambiamos un poco el código y el agregamos un else para que si es negativo imprima un cartel

de ello..

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

int numero;

printf("Escribe un numero: ");

scanf("%d", &numero);

if (numero>0) printf("El numero es positivo.\n");

else printf("El numero es negativo.\n");

getchar();

}

if-else respeta la comparación inicial que se realiza en el if, si esta es verdadera, ejecuta lo que esta

al lado del if (o entre llaves si son varias instrucciones), si es falsa ejecuta lo que esta al lado del

else ( o entre llaves si son varias instrucciones)

De esta forma si el numero es negativo va a ejecutar el printf que mostrara “El numero es

negativo”

No lo reversearemos de nuevo pero veamos en IDA la diferencia.

Page 44: c y Reversing

La comparación es similar no cambio nada, lo único que en el camino de la flecha verde o sea si la

comparación es verdadera (numero negativo) en vez de ir directamente al getchar, ahora agrego un

bloquecito que imprime el mensaje de que El numero es negativo y luego si va al getchar.

En las comparaciones se utilizan los operadores relacionales siguientes:

Si alguien tiene duda de esto por supuesto puede profundizar en la pagina 3 del curso de Cabanes.

http://www.nachocabanes.com/c/curso/cc03.php

A continuación veremos un switch en el código siguiente, como siempre si quieren una completa y

perfecta explicación de como funciona un switch en C, pues ahí esta en la pagina 3 del curso de

Cabanes, aquí daremos una muy somera.

El switch se utiliza cuando hay una gran cantidad de posibilidades de bifurcación, seria como si

evaluamos con muchos if uno tras otro, por ejemplo si un numero es cero, toma este camino, si es

Page 45: c y Reversing

uno otro camino, si es dos, otro camino, esto haría el código engorroso si lo hacemos con muchos

if, de esta forma el switch evaluá una sola vez una expresión y según su resultado sigue el camino

correspondiente.

Veamos el código así se entiende mas fácilmente.

# include <stdio.h>

main(){

funcion2();

getchar();

getchar();

getchar();

}

funcion2(){

char tecla;

printf("Pulse una tecla y luego Intro: ");

scanf("%c", &tecla);

switch (tecla)

{

case ' ': printf("Espacio.\n");

break;

case '1':

case '2':

case '3':

case '4':

case '5':

case '6':

case '7':

case '8':

case '9':

case '0': printf("Digito.\n");

break;

default: printf("Ni espacio ni digito.\n");

}

Page 46: c y Reversing

}

Se escribe a continuación de “switch” la expresión a analizar, entre paréntesis. Después, tras varias

órdenes “case” se indica cada uno de los valores posibles. Los pasos (porque pueden ser varios) que

se deben dar si se trata de ese valor se indican a continuación, terminando con “break”. Si hay que

hacer algo en caso de que no se cumpla ninguna de las condiciones, se detalla tras “default”.

Vemos que los casos que no tienen break continúan ejecutando el siguiente que viene debajo pues

no salen, tal el caso de si aprieto 1, entra al case 1, como no hay código ni break sigue al 2 y así

hasta abajo que muestra el cartel Digito y si sale porque hay un break, veamoslo en IDA a ver

como se ve el switch.

Vemos en el IDA la estructura que es un poco mas compleja.

Bueno vamos por partes, vemos abajo tres posibilidades ya que realmente en el código C vimos que

o va a Espacio, o va a Digito o va a Ni espacio ni digito

Page 47: c y Reversing

Los tres casos están claros en el IDA, puedo pintar de colores los bloques, que también es otra

ayuda para reversear así los vemos mas resaltados.

Renombro la funcion y vemos que hay dos variables, yo solo use una del tipo char que ocupa solo

un byte, la otra la agrego el compilador, así que si vemos la variable de 1 byte.

Page 48: c y Reversing

Vemos el LEA que obtiene la dirección de la variable para guardar el carácter que tipea el usuario,

vemos que %c hace que la guarde como carácter al pasar scanf.

A continuación usa la variable dword que creo, para tomar el valor de nuestro carácter y

transformarlo en un dword usando MOVSX lo cual al ser un carácter tipeado podría haber

comparado directamente el byte original, seguramente alguna causa de economía de código.

movsx eax, [ebp+caracter_tipeado]

mov [ebp+var_8], eax

Así que renombramos esta otra variable dword.

Page 49: c y Reversing

Vemos allí que luego la compara con 20 que es el espacio, así que cambiamos el 20 por espacio

haciendo click derecho y eligiendo dicha opción.

Allí vemos que cuando el carácter sea comparado y sea igual al espacio, ira al bloque verde que

imprime ESPACIO hasta aquí todo bien, cuando la comparación anterior no sea verdadera, ira por

la flecha roja aquí.

Page 50: c y Reversing

Vemos que compara nuevamente con 20 lo cambiamos también a espacio., si es menor que 20 no es

ni espacio ni dígito por lo cual el JL me lleva al bloque rosado, con lo cual ya tenemos dos de las

tres posibilidades del switch.

Si no es menor que 20 seguirá por la flecha roja a la siguiente comparación.

Allí le resta 30 al valor tipeado y lo compara con nueve y si es mas alto JA nos manda al bloque

rosado ya que seria mas alto que 39 hexa que es el carácter 9, si es menor va al cartel amarillo que

imprime Dígito pues se supone que es un valor entre 30 y 39 o sea los caracteres del 0 al 9.

Los caracteres con valor hexa del 21 al 30 son suprimidos también pues al restar con 30 queda un

numero alto mas grande que 9 ya que el JA no considera signo, así que el resultado negativo

producto de esa resta para el JA son solo valores positivos mas grandes que 9 y saltan al bloque

rosado.

Veamos este otro switch

Page 51: c y Reversing

# include <stdio.h>

main(){

funcion2();

getchar();

getchar();

getchar();

}

funcion2(){

int opcion;

printf("Donde nacio?\n");

printf("1: Argentina\n");

printf("2: Espania\n");

printf("3: Colombia\n");

printf("4: Uruguay\n");

scanf("%d", &opcion);

switch (opcion)

{

case 1: printf("Elegiste Argentina.\n");

break;

case 2: printf("Elegiste Espania.\n");

break;

case 3: printf("Elegiste Colombia.\n");

break;

case 4: printf("Elegiste Uruguay.\n");

break;

default: printf("Nada.\n");

}

}

Vemos aquí un switch con un valor numérico que proviene del scanf y se guarda como int en la

variable opción.

Page 52: c y Reversing

Vemos que es similar solo que en este caso compara directamente con el entero que tipeamos

aunque eso no evita que el compilador cree una nueva variable y copie el contenido de lo tipeado de

la nuestra a esa, sera que cuando usa switch lo debe hacer con una variable mas vaya a saber, la

cuestión es que es similar al caso anterior, pueden analizarlo si quieren.

Aquí el código que nos devuelve el HexRays muy parecido al original

int __cdecl sub_4012D0()

{

int v1; // [sp+14h] [bp-4h]@1

printf("Donde nacio?\n");

printf("1: Argentina\n");

printf("2: Espania\n");

printf("3: Colombia\n");

printf("4: Uruguay\n");

scanf("%d", &v1);

if ( v1 == 2 )

return printf("Elegiste Espania.\n");

if ( v1 > 2 )

{

if ( v1 == 3 )

return printf("Elegiste Colombia.\n");

if ( v1 == 4 )

return printf("Elegiste Uruguay.\n");

Page 53: c y Reversing

}

else

{

if ( v1 == 1 )

return printf("Elegiste Argentina.\n");

}

return printf("Nada.\n");

}

Aquí vemos un ejemplo usando while, el cual se repite mientras la condición sea verdadera o sea

mientras que el numero que tipeamos sea distinto de cero seguirá loopeando y saldrá del ciclo

cuando sea igual a cero.

# include <stdio.h>

main(){

funcion2();

}

funcion2(){

int numero;

printf("Teclea un numero (0 para salir): ");

scanf("%d", &numero);

while (numero!=0)

{

if (numero > 0) printf("Es positivo\n");

else printf("Es negativo\n");

printf("Teclea otro numero (0 para salir): ");

scanf("%d", &numero);

}

}

Veamoslo en el IDA.

Page 54: c y Reversing

Hasta aquí nada nuevo ya renombre la variable y como hemos visto el lea obtiene la dirección de la

misma que contendrá el numero tipeado por el usuario usando scanf.

Allí vemos el ciclo que se repetirá indefinidamente a no ser que la variable numero_tipeado valga

cero en ese caso ira al bloque verde que es la salida de mi funcion a través del retn.

Dentro del loop hay una comparación para ver si el numero es positivo o negativo y según eso va al

bloque rosado o al celeste imprimiendo el cartel correspondiente.

Page 55: c y Reversing

Luego vuelve a leer otro valor tipeado por el usuario y a guardarlo nuevamente en la variable

numero_tipeado y vuelve al inicio del ciclo donde chequera si es cero para salir del mismo o seguirá

repitiéndolo.

El código fuente de la funcion que nos da el HexRays es el siguiente salvo que crea una variable

mas para el valor de retorno de scanf, que nosotros no creamos.

int __cdecl funcion_investigada()

{

int result; // eax@1

int numero_tipeado; // [sp+14h] [bp-4h]@1

printf("Teclea un numero (0 para salir): ");

result = scanf("%d", &numero_tipeado);

while ( numero_tipeado )

{

if ( numero_tipeado <= 0 )

printf("Es negativo\n");

else

Page 56: c y Reversing

printf("Es positivo\n");

printf("Teclea otro numero (0 para salir): ");

result = scanf("%d", &numero_tipeado);

}

return result;

}

El siguiente es un ejemplo con for

# include <stdio.h>

main(){

funcion2();

getchar();

}

funcion2(){

int contador;

for (contador=1; contador<=10; contador++)

printf("%d ", contador);

}

Es un ciclo que tiene como primer argumento el valor donde se inicia el contador, como segundo la

comparación para chequear si continua loopeando, y el tercero es el incremento del contador.

En este caso el contador empezara desde 1 inclusive seguirá loopeando mientras sea menor o igual a

10, incrementándose de a uno.

Como dentro del loop se imprime el valor del mismo contador, pues imprimirá del 1 al 10 en este

caso.

Verlo en IDA es muy sencillo, la variable que renombramos cono contador, se inicializa en el valor

1, y se compara si es mayor que 10 (0a hexa) para ver si sale del ciclo y va al bloque verde.

Page 57: c y Reversing

Si es menor continua en el ciclo e imprime su valor y lo incrementa obteniendo en EAX su

dirección mediante lea y incrementando el contenido de EAX o sea el mismo valor del contador.

Adjunto tres ejemplos de ejecutables con goto, continue y break metidos en el medio, a ver si los

pueden reversear sin tener el código fuente, cualquier duda ya saben que break sale del ciclo,

continue vuelve al inicio del ciclo sin resetear el contador y goto va a una etiqueta como un salto

directo a la misma.

Bueno que tenga suerte y hasta la próxima

Ricardo Narvaja

Salute Crackslatinos

Page 58: c y Reversing

C Y REVERSING (parte 4) por Ricnar

Seguiremos adelante con un ejemplo de arrays, también llamado tabla, vector, arreglo o matriz, el

cual es un conjunto de elementos en el cual todos son del mismo tipo, por ejemplo:

int ejemplo[4];

Define un array de cuatro elementos en los cuales cada uno es un int, si se necesita acceder a uno

de los elementos se hará mediante un subindice entre corchetes comenzando con el valor cero para

el primer elemento y así sucesivamente, por ejemplo los elementos del array ejemplo son

ejemplo[0], ejemplo[1], ejemplo[2], ejemplo[3].

# include <stdio.h>

main(){

funcion();

getchar();

}

funcion(){

int numero[5]; /* Un array de 5 números enteros */

int suma; /* Un entero que será la suma */

numero[0] = 200; /* Les damos valores */

numero[1] = 150;

numero[2] = 100;

numero[3] = -50;

numero[4] = 300;

suma = numero[0] + /* Y hallamos la suma */

numero[1] + numero[2] + numero[3] + numero[4];

printf("Su suma es %d", suma);

}

Vemos que declara un array de 5 números enteros y a continuación lo inicializa con sus

correspondientes valores, luego halla la suma de todos y lo imprime, veamos como se ve en el IDA

todo esto.

Page 59: c y Reversing

Tenemos allí las variables que vamos a tratar de mostrar como array, es de mencionar que un array

como tiene todos los tipos de variable iguales, puede ser perfectamente declarado si uno reversea

este código, como cinco variables int independientes ya que el IDA normalmente las trata así, y

para la funcionalidad, es similar escribir el código como lo hemos hecho antes o escribirlo así.

(aunque en este caso como ya veremos no podremos usar un for para iterar por los campos del array

y habrá que sumarlos uno a uno)

# include <stdio.h>

main(){

funcion();

getchar();

}

funcion(){

int suma; /* Un entero que será la suma */

int numero0 = 200; /* Les damos valores */

int numero1 = 150;

int numero2 = 100;

int numero3 = -50;

int numero4 = 300;

suma = numero0 + /* Y hallamos la suma */

numero1 + numero2 + numero3 + numero4;

printf("Su suma es %d", suma);

}

Vemos si lo abrimos en IDA.

Page 60: c y Reversing

No hay ninguna diferencia así que se podría interpretar de ambas formas, volvamos al ejemplo

original con el array.

Volvamos al código original, veamoslo en el IDA.

Renombraremos la variable var_2c como suma ya que vemos que guarda el resultado de la misma

y no es parte del array que esta pintado en verde, luego vamos a la tabla de variables, haciendo

doble click en cualquiera de ellas.

Page 61: c y Reversing

Ahora marcamos la var_28 que es la primera variable del array y apretamos asterisco, vemos que

en la ventana nos sale que el largo de cada elemento del array que lo toma de la variable actual

donde hicimos doble click o sea var_28 es de 4 bytes, y el máximo largo posible sin pisar las

siguientes variables es 1 ya que abajo esta la var_24 pero como nosotros queremos que nuestro

array abarque 5 dwords aunque pise las variables siguientes, en tamaño o ARRAY SIZE ponemos 5

y damos OK y luego YES.

Vemos que quedo así

Ahora si vemos en el listado.

Page 62: c y Reversing

Vemos que hay una sola variable var_28 que la renombraremos a numero como en el código

original y es un array, los campos subsiguientes del array se ven como ebp+numero, luego

ebp+numero+4, y así sucesivamente.

Bueno no hay mucho mas que decir de este código, ya tenemos nuestro array, con su nombre

correcto, y sus campos creados en forma correcta, vemos que los suma y los guarda en la variable

suma la cual imprime mediante printf usando %d para mostrar su valor numérico.

Page 63: c y Reversing

Los arrays se pueden inicializar al mismo tiempo que se declaran como cualquier variable el código

en ese caso seria así y el código en el IDA es similar al visto anteriormente.

# include <stdio.h>

main(){

funcion();

getchar();

}

funcion(){

int suma; /* Un entero que será la suma */

int numero[5] = /* Un array de 5 números enteros */

{200, 150, 100, -50, 300};

suma = numero0 + /* Y hallamos la suma */

numero1 + numero2 + numero3 + numero4;

printf("Su suma es %d", suma);

}

Si utilizamos un for para sumar los miembros del array es mucho mas conveniente sobre todo si es

un array muy largo.

# include <stdio.h>

main(){

funcion();

getchar();

}

funcion(){

int suma=0; /* Un entero que será la suma */

int i;

int numero[5] ={200, 150, 100, -50, 300};

Page 64: c y Reversing

for(i=0;i<=4;i++) suma += numero[i];

printf("Su suma es %d", suma);

}

Vemos que tenemos que inicializar en este caso la variable suma con 0, pues si no dará error dentro

del for cuando quiera sumar la primera vez y no tenga valor, también debemos declarar la variable i

como int que sera el contador en el for tomando los valores de 0 a 4 para usarse como subindice de

numero[i] y sumar todos los miembros, luego al terminar el for imprimirá la suma, veamos este

ejemplo en el IDA.

Bueno iremos renombrando las variables vemos que hay dos variable que se inicializan a cero, la

var_C y var_10.

Una rápida inspección de lo que hace var_C podemos marcarla y ver donde se usa o apretar X.

Page 65: c y Reversing

Vemos que mediante un lea dentro del loop obtiene la dirección de la misma y luego le va sumando

cosas con un add, ya veremos que, pero podemos colegir que es la variable suma ya que al salir del

loop se utiliza para imprimir su valor mediante printf con el mensaje La suma es....

La renombramos como suma.

La variable var_10 es el contador del loop la llamamos i como en el código fuente, podríamos

haberla llamado contador, se ve cuando se inicializa a cero y que el máximo valor sera cuatro, y

cuando sea mas grande que 4, saldrá del loop a imprimir.

Definimos el array, realizamos los pasos como en el ejemplo anterior y quedara así.

Page 66: c y Reversing

Ahora si vemos el array con sus campos, veamos que hace dentro del loop que es lo que nos faltaba.

Vemos que EAX tomara el valor de la variable i que empieza en 0 y termina en 4 ya que es el

contador del loop.

mov edx, [ebp+eax*4+numero]

En la primera vez que loopea como EAX vale 0 se tendrá en EDX

mov edx, [ebp + numero]

Cuando EAX valga 1

mov edx, [ebp+1*4+numero]

que es igual a

mov edx, [ebp + numero + 4]

y que siempre termina siendo EDX el campo actual ya que salta de cuatro en cuatro

Page 67: c y Reversing

Así que EDX tendrá los valores de los campos en dada interación y a continuación los suma aquí

obteniendo la dirección de la variable suma y sumándole EDX a su contenido, en cada loop se le

sumara el campo siguiente, al salir del loop la variable suma tendrá la sumatoria de todos los

campos y se imprimirá como en el ejemplo original.

lea eax, [ebp+suma]

add [eax], edx

CADENAS DE CARACTERES

Las cadenas de texto se crean como arrays de caracteres, lo cual veremos como manejar en el IDA.

Las misma están formadas por una sucesión de caracteres terminada con un carácter nulo (\0), de

modo que tendremos que reservar una letra más de las que necesitamos. Por ejemplo, para guardar

el texto “Hola” usaríamos “char saludo[5]”.

# include <stdio.h>

main(){

funcion();

getchar();

getchar();

}

funcion(){

char texto[40]; /* Para guardar hasta 39 letras */

printf("Introduce tu nombre: ");

scanf("%s", &texto);

printf("Hola, %s\n", texto);

}

Allí vemos que creamos un array sin inicializar que puede guardar hasta 39 caracteres ya que debe

poner el cero final, luego mediante printf se imprime el mensaje "Introduce tu nombre: "); y

luego a scanf se le pasa la dirección de la variable texto usando el & para que llene la misma, luego

se imprime lo que el usuario tipeo en la variable texto precedido de un Hola.

Lindo código para un stack overflow jeje, así que compilemos y veamos el código en IDA.

Page 68: c y Reversing

Vemos que hay una sola variable que es nuestro array llamado texto lo renombramos.

Si vemos las variables haciendo doble click en texto.

Page 69: c y Reversing

Vemos que mas abajo están el stored ebp y el return address.

No hay que ser un genio para darse cuenta de que si el usuario tipea demasiados caracteres, como

no hay ningún chequeo ni nada, terminara pisando ambos y al continuar la ejecución del programa y

llegar el retn y al salir de mi funcion habré redirigido el programa a la dirección que halla quedado

allí en el return address pisado por mi, según con que caracteres lo haya hecho, pero bueno que esto

no es un curso de exploits, aunque es bueno remarcarlo je.

En el caso de los arrays volveremos a usar el comando de IDA llamado ARRAY , apretando

asterisco cobre la variable texto.

Allí IDA se da cuenta que podría crear un ARRAY de hasta 56 bytes antes de pisar el stored ebp y

el return address, como nosotros sabemos que nuestro array es menor elegimos 40 de largo igual el

procesador reserva un poco mas de lugar del necesario, si eligiéramos 56 funcionaria de la misma

forma.

Page 70: c y Reversing

De esta forma quedo el código así, no se aprecian camios pues no existe el usa de caracteres

intermedios del array, si fuera ese el caso si se accediera a un carácter especifico por posición

usando los corchetes como subindices, el código habría cambiado, lo cual veremos en el siguiente

ejemplo.

El código es el siguiente:

# include <stdio.h>

main(){

funcion();

getchar();

getchar();

}

Page 71: c y Reversing

funcion(){

char texto[40];

printf("Introduce tu nombre: ");

scanf("%s", texto);

printf("Hola, %s. Tu inicial es %c\n", texto, texto[0]);

}

Vemos que luego de crear el array de 40 caracteres inclusive el cero final, luego hace un printf

usando un doble format string, primero con %s y el texto tipeado completo como string, y luego

%c con solo el primer carácter que obtiene usando el subindice cero (texto[0])

Si vemos en el IDA el código y renombramos convenientemente.

Vemos que le pasa la dirección de la variable texto mediante el LEA para qiue el usuario lo llene

usando scanf.

Vemos que con movsx mueve el primer byte de lo tipeado y lo pasa como argumento al stack para

el format string %c, luego pasa la dirección a la string completa con lea, para hacer el otro format

string %s.

Definiendo el array.

Page 72: c y Reversing

Al ver el código vemos que no hubo variación ya que usa solo el primer carácter si cambiamos el

código para que use el segundo.

# include <stdio.h>

main(){

funcion();

getchar();

getchar();

}

funcion(){

Page 73: c y Reversing

char texto[40];

printf("Introduce tu nombre: ");

scanf("%s", texto);

printf("Hola, %s. Tu inicial es %c\n", texto, texto[0]);

printf("Hola, %s. Tu segunda letra es %c\n", texto, texto[1]);

}

Vemos en el IDA que ahora tuvo que crear una variable para el segundo carácter, así que creo dos

variables de un byte lo cual no es lo mas aproximado al código nuestro ya que nosotros usamos los

dos primeros caracteres de un array y no dos variables char sueltas.

Si marcamos la variable superior y definimos el array de 40 caracteres de largo.

Page 74: c y Reversing

Vemos que ahora si tenemos una sola variable array y si vemos el código, este cambio.

Vemos las dos flechas rojas leen el primer carácter y el segundo carácter, pero esta vez como primer

y segundo campo de un array y no como variables separadas el resto es similar al ejemplo anterior.

Bueno en la parte 5 seguimos con mas arrays y estructuras.

Hasta la parte 5

Ricardo Narvaja

Page 75: c y Reversing

C Y REVERSING (parte 5) por Ricnar

Si no se dieron cuenta en los ejercicios anteriores pueden probar y verán que scanf a pesar de que te

deja tipear después de un espacio, solo lee hasta allí, por ejemplo.

# include <stdio.h>

main(){

funcion();

getchar();

getchar();

getchar();

}

funcion(){

char texto[40]; /* Para guardar hasta 39 letras */

printf("Introduce tu nombre: ");

scanf("%s", &texto);

printf("Hola, %s\n", texto);

}

Si ejecuto esto y tipeo pepe genio

Veo que imprime solo pepe.

Si queremos que un programa haga entrada por teclado sin detenerse en un espacio deberemos usar

gets() en vez de scanf, aquí el ejemplo y para tener una mejor salida si es solo imprimir en pantalla

y no necesita format string, podemos usar puts() en vez de printf.

# include <stdio.h>

main(){

funcion();

getchar();

}

funcion(){

Page 76: c y Reversing

char texto[40]; /* Para guardar hasta 39 letras */

puts("Introduce tu nombre: ");

gets(texto);

printf("Hola, %s\n", texto);

}

El printf final como no es solo imprimir en pantalla sino que tiene un format string, no podemos

reemplazarlo por un puts, si corremos este ejemplo vemos que ahora respeta los espacios.

Si lo vemos en IDA.

Vemos en amarillo que le pasa como argumento la dirección donde se encuentra la string al puts,

aunque no lo hayamos especificado con & en el código fuente, y que el gets también usa la

dirección de nuestra variable texto como argumento, en estos casos aun sin ser necesario en el

código fuente aclararlo ya gets y puts interpretan que necesitan la dirección de la string, por

supuesto el printf para hacer el format string con un %s necesita también la dirección de la misma o

sea de la variable texto y también usa lea para hallarla.

Si le agregamos la siguiente linea

# include <stdio.h>

main(){

funcion();

getchar();

Page 77: c y Reversing

}

funcion(){

char texto[40]; /* Para guardar hasta 39 letras */

puts("Introduce tu nombre: ");

gets(texto);

printf("Hola, %s\n", texto);

printf("Has tecleado %d letras", strlen(texto));

}

Vemos que usara strlen para hallar el largo de la string que tipeamos y luego pasara ese valor

numérico como format string %d dentro del mensaje Has tecleado XXX letras.

Por supuesto el espacio vacío es un carácter valido por eso cuenta 9 letras.

Bueno a strlen se le pasa también la dirección de nuestra variable texto, y el resultado que devuelve

EAX en rosado, lo pasa como argumento de printf para hacer el format string e imprimir el numero

de caracteres con %d.

Copio este texto del curso de Cabanes que explica como copiar cadenas y esta claro

Page 78: c y Reversing

Asignando a una cadena el valor de otra: strcpy, strncpy; strcat

Cuando queremos dar a una variable el valor de otra, normalmente usamos construcciones como a

=2, o como a = b. Pero en el caso de las cadenas de texto, esta NO es la forma correcta, no podemos

hacer algo como saludo="hola" ni algo como texto1=texto2. Si hacemos algo así, haremos que las

dos cadenas estén en la misma posición de memoria, y que los cambios que hagamos a una de ellas

se reflejen también en la otra. La forma correcta de guardar en una cadena de texto un cierto valor

es:

strcpy (destino, origen);

Es decir, debemos usar una función llamada “strcpy” (string copy, copiar cadena), que se encuentra

también en “string.h”. Vamos a ver dos ejemplos de su uso:

strcpy (saludo, "hola");

Es nuestra responsabilidad que en la cadena de destino haya suficiente espacio reservado para

copiar lo que queremos. Si no es así, estaremos sobreescribiendo direcciones de memoria en las que

no sabemos qué hay.

Para evitar este problema, tenemos una forma de indicar que queremos copiar sólo los primeros n

bytes de origen, usando la función “strncpy”, así:

strncpy (destino, origen, n);

Vamos a ver un ejemplo, que nos pida que tecleemos una frase y guarde en otra variable sólo las 4

primeras letras:

# include <stdio.h>

# include <string.h>

main(){

funcion();

getchar();

}

funcion(){

char texto1[40], texto2[40], texto3[10];

printf("Introduce un frase: ");

gets(texto1);

strcpy(texto2, texto1);

printf("Una copia de tu texto es %s\n", texto2);

strncpy(texto3, texto1, 4);

printf("Y sus 4 primeras letras son %s\n", texto3);

}

Page 79: c y Reversing

El problema con strncpy es que copia solo 4 caracteres no pone el cero final como si hace strcpy

que copia hasta que encuentra el cero final incluyéndolo y de esta forma si en la variable había

basura se mostrara como vemos en el ejemplo, después del cuarto carácter.

# include <stdio.h>

# include <string.h>

main(){

funcion();

getchar();

}

funcion(){

char texto1[40], texto2[40], texto3[10];

printf("Introduce un frase: ");

gets(texto1);

strcpy(texto2, texto1);

printf("Una copia de tu texto es %s\n", texto2);

strncpy(texto3, texto1, 4);

texto3[4] = '\0';

printf("Y sus 4 primeras letras son %s\n", texto3);

}

Allí le ponemos el cero final manualmente y si lo ejecutamos.

Vemos que el cero que colocamos al final corta la string, de esta forma no importa que haya habido

basura anterior.

Page 80: c y Reversing

Aquí vemos cuatro variables, nosotros creamos 3 que son arrays de caracteres, así que vayamos

observando con cuidado para crear los arrays en forma correcta, la primera variable que vemos que

usa es var_38 que es para guardar el primer texto que tipea el usuario, en nuestro código dicha

variable se llama texto1 y tiene 40 caracteres de largo incluyendo el cero final.

Así que vamos a la tabla de variables con doble click en una de ellas.

Vemos que usando todo el espacio disponible hasta el stored ebp, este array podría tener de largo

56 y no habría problema, pues no pisa nada, si reverseamos y no sabemos el largo exacto porque no

tenemos el código fuente, poner 56 seria correcto, pues abarcamos todo el espacio disponible y

funcionaria igual, como sabemos que es 40 el largo lo haremos así pero ambas posibilidades son

correctas.

Page 81: c y Reversing

La siguiente variable que usa es var_68 que la usa en el strcpy para obtener una copia del texto

tipeado en otra variable, en nuestro código se llama texto2 y también su largo es 40.

El espacio máximo posible es 48 sin pisar nada, podríamos dejarlo así, pero ponemos 40 para

quedar igual al código fuente.

La tercer variable es la que guardara los 4 bytes y esa es texto3 que tiene largo de 10 caracteres.

Vemos que sin pisar otra variable tendría como largo 4 ya que a continuación viene la var_74 que

en realidad es el quinto carácter de nuestro array texto3 que usa para ponerle el cero final, así que si

abarcamos con el tamaño igual a 10, nos quedaremos con solo 3 variables, y la cuarta var_74

desaparecerá y pasara a ser un campo del array, donde se pone el cero.

Page 82: c y Reversing

Ahora si vemos el código:

Vemos que ahora si tenemos nuestras tres variables tipo array como en el código fuente y al

convertir texto3 en array desapareció la variable de mas que usaba para modificar un campo

intermedio del mismo para poner un cero en el 5 lugar, ahora mostrándose como campo del mismo

array.

Puse dos ejercicios con arrays para reversear a ver quien me manda a mi privado el código fuente

Page 83: c y Reversing

aproximado reverseado.

Hasta la parte siguiente

Ricardo Narvaja

Page 84: c y Reversing

C Y REVERSING (parte 6) por Ricnar

MOLDES O CASTING ( CASTEO)

En C tenemos la posibilidad de forzar y transforma una variable que esta declarada de un tipo a

otro tipo usando el llamado casteo o moldeo de la misma, veamos varios ejemplos:

Ejemplo 1:

Veamos este código:

# include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

float a = 5.25;

int b = (int)a;

printf ("%d\n", b);

}

Es una variable llamada a declarada como float y asignado un float 5.25 que se castea poniéndole

el (int) delante y a no cambia sigue siendo float pero al usar (int)a convertirá el valor a int y lo

asignara en b como int, vemos al imprimir el valor de b que vale 5.

Page 85: c y Reversing

Si arrancamos el debugger del IDA y paramos en el inicio

Cambiamos el stack para ver el stack de punto flotante.

Cambiamos el valor para que lo muestre como punto flotante (yo ya lo había hecho en la primera

imagen, lo volví a repetir para que vea como se hace.

Vemos que var_4 es inicializada con el valor 5.25 float, así que la renombramos como en el código

fuente con el nombre a, recordamos que el sizeof en C de una variable float es 4, así que dword

esta correcto en IDA como largo de la variable a.

Page 86: c y Reversing

FLD carga el valor de a en ST0 del stack de punto flotante.

Hay un par de instrucciones con el Control Word que no son relativas a nuestro valor y aquí.

fistp [ebp+var_8]

FIST convierte a int el float y lo popea de ST0 a var_8 en este caso.

Quiere decir que podemos renombrar var_8 con el nombre b como en el código fuente, las otras

dos variables auxiliares son usadas para trabajar con el control word del punto flotante y no son del

código fuente nuestro así que las dejamos como están.

Luego de otra instrucción con el control word seguimos la variable b que la pasa al stack para

usarlo como argumento de printf, ya vimos que es un int que vale 5 y allí mediante el format string

%d imprimirá su valor.

.text:004012EF mov eax, [ebp+b]

.text:004012F2 mov [esp+4], eax

.text:004012F6 mov dword ptr [esp], offset aD ; "%d\n"

.text:004012FD call printf

El siguiente ejemplo es un casteo de char a int.

# include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

char b = 'a';

int x = (int)b;

printf ("%d", x);

Page 87: c y Reversing

}

Allí vemos el casteo de la variable char a int y imprime el valor ASCII del carácter a en decimal,

veamos como se ve en el IDA.

Allí vemos como mueve a la variable declarada como char que aquí es un byte el valor 61h que es

la a minúscula lo cambiamos para que muestre la a con el menú del click derecho y renombramos la

variable como b.

movsx eax, [ebp+b]

mov [ebp+var_8], eax

Allí vemos como toma el valor y lo convierte a int mediante el movsx y lo guarda en var_8 que es

la otra variable int así que la renombramos a x.

Page 88: c y Reversing

Luego vemos que sencillamente imprime su valor.

Muchas veces al imprimir el valor haciendo printf este hace un casting on the fly para hacer format

string, depende de lo que le coloquemos luego del %, pero hay que saber que esto es solo visual, si

una variable no fue casteada antes, y solo se muestra por consola su salida en un formato diferente,

si luego se siguen realizando operaciones con la misma variable sin castear, esta seguirá en su tipo

original, por lo cual es importante realizarlo antes del printf en nuestro codigo fuente, en el caso

anterior vemos que al pasar la variable x casteada como int al hacer printf se vera el valor int y

ademas x queda siendo una variable int, mientras que si no casteamos antes y hacemos el printf de

la variable b que es un char si ponemos que en el printf lo muestre como %d lo hará, mostrara el

valor como si fuera un int, por el casting on the fly, pero no transformara la variable b la cual

seguirá siendo de tipo char y para futuras operaciones podría fallar si la usamos como int.

Aquí vemos ese ejemplo claramente el resultado de ambas variables mostrado es similar por el

casteo al vuelo para hacer printf, pero si vemos en IDA veremos que x y b son variables de

diferente tamaño y tipo.

Page 89: c y Reversing

# include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

char b = 'a';

int x = (int)b;

printf ("%d\n", x);

printf ("%d\n", b);

}

Vemos que en el primer printf que usa la variable x que es un int, la pasa directamente como

argumento tipo dword para el printf usando %d, mientras que en el segundo printf, lo convierte

temporalmente a dword al vuelo, sin cambiar la variable que es un byte al moverlo a EAX el cual

queda como dword y es pasado como argumento de printf y %d, por lo tanto el casteo es on the

fly y no fue guardado ni cambiado nada, b sigue siendo un char de un byte y seguirá por el resto

del programa siendo de un byte, así que no hay que confundirse, no pensar que porque printf lo

muestre como int la variable es un int, aunque visualmente el resultado sea el mismo.

Bueno terminamos lo de casting, quisiera que resuelvan los ejercicios ambos tienen casting, y

quisiera que no solo alguien me enviara el código fuente sino el tute con el reversing explicado así

me salvo de hacerlo yo, jeje

Hasta la parte 7

Ricardo Narvaja

Page 90: c y Reversing

C Y REVERSING (parte 7) por Ricnar

Tablas bidimensionales

Podemos declarar tablas de dos o más dimensiones. Por ejemplo, si queremos guardar datos de dos

grupos de alumnos, cada uno de los cuales tiene 20 alumnos, tenemos dos opciones:

1) Podemos usar int datosAlumnos[40] y entonces debemos recordar que los 20 primeros datos

corresponden realmente a un grupo de alumnos y los 20 siguientes a otro grupo.

2) O bien podemos emplear int datosAlumnos[2][20] y entonces sabemos que los datos de la forma

datosAlumnos[0][i] son los del primer grupo, y los datosAlumnos[1][i] son los del segundo.

En cualquier caso, si queremos indicar valores iniciales, lo haremos entre llaves, igual que si fuera

una tabla de una única dimensión. Vamos a verlo con un ejemplo de su uso:

PD :Esta explicación la copie del curso de C de Cabanes el que necesita la explicación completa la

puede obtener aquí:

http://www.nachocabanes.com/c/curso/cc05.php

El primer ejemplo es este:

# include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

int notas[2][10] =

{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,

11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };

printf("La nota del tercer alumno del grupo 1 es %d",

notas[0][2]);

}

Vemos que crea una tabla de dos dimensiones o sea 2 x 10, y le asigna los 20 valores tipo int, los 10

primeros corresponden al primer grupo de alumnos, y los 10 siguientes al segundo grupo de

alumnos, luego imprime la nota del tercer alumno del primer grupo que como los campos se

numeran desde cero, sera el valor de la tabla notas (0,2).

Page 91: c y Reversing

Bueno aquí el IDA la embarro bastante, por supuesto no interpreto como array de dos dimensiones

o tabla, si no pescaba los array de una dimensión menos pescara los de dos jeje.

Vemos que creo una variable de un byte y otra de un word, si vemos el espacio que hay entre

variables, vemos que es correcto, pues.

Vemos apretando asterisco en la primera variable, que la misma podría llegar a extenderse 8 bytes

hasta llegar a la otra, ya que como IDA la definió como byte, el máximo largo 8 se refiere a 8 del

mismo tipo o sea 8 bytes.

Page 92: c y Reversing

Si hacemos lo mismo en la siguiente que esta definida como dword.

Vemos que su largo es 20 dwords o sea 80 bytes o sea que el espacio total es 80.

Bueno cambiaremos la primera variable superior a dword y luego apretamos asterisco y le damos el

espacio que sea la suma de los 2 dwords de la primera (8 bytes) mas los 20 dwords de la segunda

variable (80 bytes) así que en total serán 22 dwords o sea 88 bytes un poquito mas largo que el

nuestro pero no hay problema mientras no pisemos el stored_ebp y el return address.

Para cambiar a dword una variable en IDA, nos situamos en la definición de la misma donde dice

db (byte) y apretamos la tecla D, primero cambiara a dw(no confundir eso es word) y luego

cambia a dd (dword)

Apretando la tecla D vemos que quedo dd como la otra variable

Si vemos en el código:

Page 93: c y Reversing

Ahora las dos variables son DWORD, ahora volvamos a la tabla de variables y apretemos * y

pongamos el largo máximo 22 dwords.

Me olvide de decir que otra forma de saber cuanto es el largo máximo desde esta primera variable,

hasta el stored_ebp sin pisarlo, sin hacer cuentas es eliminar la segunda variable ya que la vamos a

pisar, haciendo click derecho y undefine, así desaparece.

Ahora si apretamos asterisco en la primera vemos que nos dice 22 dwords el espacio máximo ya

que no cuenta la segunda variable como pisada ya que no existe y cuenta hasta el stored_ebp.

Ahora si quedo una sola variable y todo completo.

De cualquier forma vemos que a diferencia de nuestro código quedo un array unidimensional con

un solo index que lo recorre entero, mientras que en nuestro código fuente era un array

bidimensional con dos indices que recorrían los dos grupos de 10 alumnos cada uno.

Page 94: c y Reversing

Quiere decir que donde nuestro código muestra imprimir el elemento (0,2) de una tabla de dos

dimensiones de 10 elementos cada campo, en el código del IDA sera un solo indice de 0 a 19 y el

buscado sera el tercer elemento de este array.

Veamos como terminar de interpretarlo así, y luego veremos que podemos hacer para poner doble

indice ya que el IDA no nos deja la posibilidad usando array.

Vemos que inicializara usando memcpy, que necesita tres argumentos, el tamaño de lo que va a

copiar en bytes, la dirección desde donde copiar o fuente, y la dirección de destino adonde copiara o

destination.

Sera el size de la copia 50h o sea 80 bytes que es el largo de las 20 notas en dword, si vamos a

402000 que es la dirección de la fuente veremos las notas.

Allí las vemos, podemos también aquí darle formato de array, primero cambiamos db por dd ya que

son dwords allí en 402000 apretando D dos veces.

Ahora apretamos asterisco.

Page 95: c y Reversing

El array element es 4 porque cada elemento es un dword, el máximo posible size antes de pisar algo

(en este caso otra referencia) es 21 dwords, pero como sabemos que son 20 los datos, cambiamos a

20, y como son dos grupos de a 10, podemos poner en la segunda linea 10, para que muestre como

dos grupos de a 10, lo cual no tiene efecto en el index, seguirá siendo un array unidimensional pero

aquí en la memoria la creación de arrays es mas flexible al no haber variables que respetar así que

lo muestra correctamente en dos lineas.

Por ultimo al poner -1 en el ancho mostrado de cada elemento los agrupa para que se vean mas

compactos y no deja espacio entre cada uno.

Así se ve mejor en la memoria acomodados los datos.

Si posamos el mouse en la flechita que nos muestra la referencia desde donde es llamado este array

vemos el código en el popup y marcado cuando llama a 402000 para pasarlo a EDX y usarlo como

dirección de la fuente de datos del memcpy, por supuesto el destination o donde copiara estos datos

sera nuestra la dirección de la variable notas en el stack, y el largo 80 bytes o sea 50h.

Page 96: c y Reversing

Luego de copiar la data leerá la tercer nota, ya que desde el inicio de notas, como son dwords, la

tercera estará 8 bytes mas adelante y luego imprimirá ese valor.

mov eax, [ebp+notas+8]

Si alguien probo vemos que cambiando en la memoria la forma de visualización por dos lineas de

10, mejoro la misma, pero en el stack cuando creamos nuestra variable notas si pones 10 en el

mismo casillero no hace un array bidimensional ni modifica la forma de usar los indices, así que

veremos si podemos acomodarlo de otra forma para que se adapte mejor a nuestro código ya que

revisando los libros no encontré forma de hacerlo con arrays, así que les mostrare como lo hago yo,

no se si es la mejor forma pero al menos quedara con doble subindice de la misma forma que el

código original.

Cambiemos notas de nuevo a que sea una variable dword como era originalmente, este método se

puede aplicar sin necesidad de hacer desaparecer la otra variable que había, funciona igual y la pisa

tal cual como los arrays.

Abramos la ventana de estructuras, no tiemblen que no es tan difícil como lo pintan jeje

Page 97: c y Reversing

Ahí vemos que apretando INS o la tecla INSERT creamos una estructura nueva, lo hacemos con

estructuras porque un array bidimensional es una forma de estructura, lo cual no hemos visto aun,

pero sabemos que una estructura puede contener diferentes tipos de campos mezclados, y en nuestro

caso contendrá dos arrays de 10 dwords cada campo.

No importa el nombre demos OK.

D/A/* : create structure member (data/ascii/array)

Vemos en la aclaración que para agregar algo debemos apretar D para datos, A para ASCII y

asterisco para arrays, hagamos esto ultimo.

Page 98: c y Reversing

Vemos que nos creo un array de un solo byte arriba nos dice el tamaño de la estructura que por

ahora es uno.

Marcando allí y apretando la D nos deja cambiarlo a DWORD ahora apretamos asterisco de nuevo.

Allí vemos que cada elemento sera un dword y que el tamaño máximo aquí no esta limitado pues

estamos en una definición de estructura fuera del programa, igual sabemos que cada array debe

tener 10 dwords así que ponemos 10.

ya tenemos el primer array para agregar un segundo debemos ir al final a ends y apretar de nuevo

asterisco y repetir lo mismo quedara así.

Page 99: c y Reversing

Listo ya esta creada ahora volvamos a las variables.

Marquemos notas y apretemos ALT mas Q.

Ahí sale la que creamos para elegir, damos OK.

Si pasamos el mouse por notas vemos que ahora nos muestra la definición de la estructura.

Page 100: c y Reversing

Vemos que ahora si la variable notas tiene doble subindice, ya que el primer grupo de 10 se llama

field0, y el segundo field1, podemos renombrarlos en la misma estructura.

Page 101: c y Reversing

Ahora si vemos una estructura que es un array bidimensional con dos indices, ahí vemos que al leer

la nota del primer grupo, usa el primer array grupo_1 y indexa por este hasta el tercer elemento del

mismo que sera el que esta 8 bytes mas adelante, no imprimimos un elemento del segundo grupo

pero por ejemplo si lo hubiéramos hecho este aparecería como notas.grupo_2+ x, usando el otro

indice.

Lo mas bonito es que como ya tenemos definida la estructura podemos ir a 402000 y cambiar a que

en vez de un array sea la misma estructura esta, apretando ALT mas Q y seleccionándola, quedara

así.

Vemos que marca al final de cada uno el nombre de los arrays grupo_1 y grupo_2.

Bueno esto ademas de servirnos para ver arrays bi o multidimensionales, no sirvió para iniciarnos

con simples estructuras, las cuales profundizaremos en la parte siguiente.

Hasta la próxima

Ricardo Narvaja

Page 102: c y Reversing

C Y REVERSING (parte 8) por Ricnar

ESTRUCTURAS

Hemos utilizado en la parte anterior en IDA estructuras para solucionar parcialmente, la poca

posibilidad que al menos yo conozco en el mismo para manejar arrays o tablas de varias

dimensiones.

Ahora veremos el tema estructuras en C códigos de ejemplos y reversearemos los mismos.

Una estructura es una agrupación de datos, los cuales no necesariamente son del mismo tipo. Se

definen con la palabra “struct”.

Para acceder a cada uno de los datos, tanto si queremos leer su valor como si queremos cambiarlo,

se debe indicar el nombre de la variable o instancia y el del dato (o campo) separados por un punto:

Veamos un ejemplo:

# include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

struct mia

{

char inicial;

int edad;

float nota;

} persona;

persona.inicial = 'J';

persona.edad = 20;

persona.nota = 7.5;

printf("La edad es %d", persona.edad);

}

Vemos resaltado la definición de la estructura llamada mia, y sus campos, al final del mismo se

coloca el nombre de la instancia en este caso persona, el nombre mia puede no estar si no vamos a

realizar mas de una instancia de este mismo tipo, en el caso de que lo coloquemos podremos

instanciar mas variables con la misma estructura, la cual tiene tres campos un char llamado inicial,

un int llamado edad y un float llamado nota, luego vemos como se inicializan los tres campos y

luego como se imprime persona.edad.

Page 103: c y Reversing

Si lo ejecutamos vemos.

Si quisiéramos realizar otra instancia en mia, podríamos agregar este código:

# include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

struct mia

{

char inicial;

int edad;

float nota;

} persona,pepe;

persona.inicial = 'J';

persona.edad = 20;

persona.nota = 7.5;

printf("La edad es %d\n", persona.edad);

pepe.edad=30;

printf("La edad de pepe es %d\n", pepe.edad);

}

Esta seria una posibilidad en la declaración de las estructuras, poner las instancias separadas por

comas en este caso le agregamos la instancia pepe, pero en el caso que quisiéramos agregar otra

instancia mas adelante en el programa, podríamos hacerlo así.

# include <stdio.h>

main(){

Page 104: c y Reversing

funcion1();

getchar();

}

funcion1(){

struct mia

{

char inicial;

int edad;

float nota;

} persona;

persona.inicial = 'J';

persona.edad = 20;

persona.nota = 7.5;

printf("La edad es %d\n", persona.edad);

struct mia pepe;

pepe.edad=30;

printf("La edad de pepe es %d\n", pepe.edad);

}

Vemos que ahora quitamos pepe de la definición de estructura mia, pero lo agregamos

posteriormente como otra instancia, como vemos en la instrucción resaltada y haciendo uso del

nombre de la estructura para saber que se agrega a ella, si no tuviera nombre no podríamos

adicionarle mas instancias al vuelo que las definidas originalmente.

Veamos el caso mas sencillo en el IDA

main(){

funcion1();

getchar();

}

funcion1(){

struct mia

{

char inicial;

int edad;

float nota;

} persona;

persona.inicial = 'J';

persona.edad = 20;

persona.nota = 7.5;

printf("La edad es %d", persona.edad);

Page 105: c y Reversing

}

Como vemos siempre IDA nos muestra todas variables sueltas

En este caso al campo de la estructura llamado inicial que era un char aquí lo muestra como una

variable char suelta, a los otros dos campos int, llamados edad y nota también.

Bueno debemos ir a la ventana estructuras y crear una estructura acorde a los tres campos que

tenemos en ella, sabemos que un array no es pues el mismo no puede contener diferentes tipos de

datos, podrían ser solo int o solo char, por ejemplo, pero en este caso hay un campo char y dos int

así que no tenemos duda al reversear que es una estructura.

En la ventana de estructuras apretamos INSERT y ponemos el nombre de la misma en este caso mia

y damos OK.

Page 106: c y Reversing

Ahora hay que agregar un char en este caso apretamos la D y sale ya directamente un campo db.

El nombre de este campo es inicial así que lo renombramos.

Ahora vamos a ends y apretamos D de nuevo pero ahora luego que aparece el nuevo campo sera

db, vamos alli y apretando D nuevamente hasta cambiar a dd.

Debería quedar así ya que edad y nota son dwords.

Ya tenemos la estructura creada ahora vayamos a nuestras variables.

Page 107: c y Reversing

Marcamos la variable superior y apretamos ALT mas Q.

Elegimos la estructura mia que esta allí y apretamos OK.

Page 108: c y Reversing

Listo vemos que al lado de var_18 donde dice el tipo de datos, ahora dice mia y que si ponemos el

mouse sobre var_18 nos muestra como es la estructura que tiene 9 bytes de largo ya que tiene un

char de un byte y dos dwords de 4 bytes cada uno.

Ahora renombramos var_18 con el nombre de nuestra instancia de la estructura, la llamamos

persona como en el código fuente.

Si vemos el código vemos que para que sea perfecto hemos cometido una falla.

Page 109: c y Reversing

Vemos la instancia persona del tipo mia bien creada pero el indice vemos que no guarda en

persona.edad el valor sino en persona.edad + 3 y esto es por lo siguiente, a veces hay que tener

cuidado pues las variables originales no estaban pegadas como nosotros armamos la estructura si las

vemos

Vemos que luego de la primera variable que era un byte y que es el primer campo hay tres bytes sin

usar y luego si el segundo campo, así que si nosotros creamos la estructura acorde a esto debemos

meter 3 bytes de relleno, que aunque no usemos, mantendrán los indices en forma correcta,

volvamos a la definición de la estructura mia.

Page 110: c y Reversing

La borramos y la creamos nuevamente ahora luego del primer campo inicial que es un byte

agregamos tres campos mas de byte con cualquier nombre y luego los otros dos campos dwords

edad y nota.

Volvemos a la definición de las variables si no nos aparece ninguna variable, lo cual es lo mas

posible y para que quede el código como original, en la funcion nuestra, apretamos click derecho

UNDEFINE

Page 111: c y Reversing

Después en la misma linea click derecho CODE

y al final click derecho CREATE FUNCION

Este es un truco que siempre funciona y restaura una funcion a como era originalmente sin tener

que cargar todo de nuevo ni reiniciar si nos mandamos alguna macana.

Page 112: c y Reversing

Apretando la barra espaciadora se cambia a modo gráfico, ahora vamos a las variables y le

asignamos a var_18 con ALT mas Q la estructura mia.

Ahora veamos el código, renombrando var_18 como persona.

Page 113: c y Reversing

Vemos que los indices quedaron correctos y inicializa correctamente persona.inicial con 4ah que

podemos hacer click derecho y cambiarlo al carácter J, luego persona.edad con 14h y

persona.nota con el valor float que corresponde a 7.5 que también cambiaremos marcándolo y

yendo a EDIT-OPERAND TYPE-NUMBR-FLOATING POINT.

Ahora si que quedo perfecto jeje.

No quiero meter mil ejemplos todos juntos en una sola parte para ir de a poquito con este tema en

la parte siguiente veremos mas sobre estructuras y ejemplos mas complicados pero quiero que

digieran bien antes esta parte para luego continuar sin atorarse.

Hasta la parte siguiente:

Ricardo Narvaja

Page 114: c y Reversing

C Y REVERSING (parte 9) por Ricnar

ARRAY DE ESTRUCTURAS

Como ya vimos lo que es un array y ya vimos lo que es una estructura, pues es fácil deducir que un

array de estructuras, es un array cuyos campos son todos iguales y son estructuras jeje.

Si al código del ejemplo anterior lo cambiamos un poco

#include <stdio.h>

main(){

funcion1();

getchar();

}

funcion1(){

struct mia

{

char inicial;

int edad;

float nota;

} persona[20];

persona[1].inicial = 'J';

persona[2].edad = 20;

persona[11].nota = 7.5;

printf("La edad es %d", persona[2].edad);

Vemos que ahora persona es un array de 20 campos del tipo mia cada uno, o sea que habrá un

persona[0] donde se podrá setear su inicial, edad y nota, un persona[1], etc así sucesivamente, si lo

corremos funciona pues imprimimos persona[2].edad que lo acabamos de inicializar antes.

Veamos en IDA a ver como podemos mostrarlo en una forma que respete los indices y nos

manejemos como la misma variable que el código fuente o sea un array de estructuras.

Page 115: c y Reversing

Las variables son como en el ejemplo anterior, una de un byte y dos dwords y como vimos el IDA

crea variables, para los campos de un array o una estructura que va a usar, los otros que no usa

quedan indefinidos, vemos la var_EC que es un char para guardar la letra J en persona[1].inicial,

luego un montón de bytes indefinidos que son los campos que no usa o no se inicializan, luego lo

mismo los dos dwords para guardar los valores de persona[2].edad y persona[11].nota.

Vamos a estructuras y creamos la misma que antes con los tres bytes basura

Page 116: c y Reversing

Vemos que considerando los tres bytes basura la estructura tiene 0Ch de largo o sea 12 y que si la

hacemos de ese tamaño, luego hay 4 bytes que son el primer char del segundo campo, luego los tres

bytes basura o sea 4 bytes en total y ya viene la segunda variable que es un dword sin estar

desfasada así que poner los bytes de relleno parece estar bien sigamos.

Hagamos que var_EC sea del tipo de la estructura mia.

Vemos que coincide pues una ves que var_EC es del tipo mia y abarca 12 bytes como dije, en

amarillo se ve en la imagen que viene el byte que corresponde al siguiente campo, sera

persona[2].inicial, luego en rosa los tres bytes basura y en verde que es lo importante viene el

dword para persona[2].edad que es el que debe coincidir para var_DC.

Ahora como cuando hicimos los arrays vimos que como son todos los campos similares cuando

definimos el primero, solo hay que apretar asterisco en el mismo y poner el largo, en este caso,

ponemos el cursor en var_EC, apretamos asterisco y nos sale.

Page 117: c y Reversing

Si no tenemos ganas de hacer cuentas como sabemos que las otras dos variables que hay debajo

pertenecen a este array de estructuras, las undefinimos para que desaparezcan y luego volvemos a

apretar el asterisco.

Page 118: c y Reversing

Vemos que nos dice que puede haber solo 19 campos como máximo, jeje hemos cometido un error,

y eso es porque no nos dimos cuenta que la primera variable que creo IDA var_EC, corresponde a

persona[1], pero hay un persona[0] justo arriba de 12 bytes que no esta dentro del array que

creamos.

Ahí esta si contamos 12 hacia arriba desde var_EC, encontramos el inicio verdadero que esta en

F8, allí apretamos ALT mas Q y le asignamos un tipo de estructura mia.

Page 119: c y Reversing

Ahora podemos undefinir la variable var_EC para ver cuanto espacio queda al apretar asterisco.

Ahora si ponemos 20 y vemos que cada campo tiene 12 bytes de largo.

Renombramos a persona.

Page 120: c y Reversing

Allí vemos que ahora los indices coinciden pues como cada campo tiene 0ch de largo al dividir el

indice por 0c nos dice que campo es.

Como 0Ch / 0Ch es igual a 1

persona.inicial+0Ch es persona[1].inicial

Como 18h / 0Ch es igual a 2

persona.nota+18h es persona[2].nota

Como 84h / 0Ch es igual a Bh o sea 11

persona.edad+84h es persona[11].edad

Coinciden los indices con la estructura de nuestro código fuente.

Ahí puse un ejemplo para reversear que incluye en un solo ejecutable casi todo lo que vimos hasta

ahora, tiene switchs, estructuras, strings, loops, bah un poco de todo veamos si pueden hacerlo, sino

lo haré yo en la parte siguiente.

Suerte que es bravo jeje

Hasta la parte 10

Ricardo Narvaja

Page 121: c y Reversing

C Y REVERSING (parte 10) por Ricnar

MANEJO DE ARCHIVOS O FICHEROS

Poco a poco vamos avanzando en diferentes temas relativos a C y su reverseo, ahora nos toca el

tema de manejo de archivos, la lectura y escritura en ellos.

#include <stdio.h>

#include <string.h>

main(){

funcion();

getchar();

}

funcion()

{

FILE* fichero;

fichero = fopen("prueba.txt", "wt");

fputs("Esto es una línea\n", fichero);

fputs("Esto es otra", fichero);

fputs(" y esto es continuación de la anterior\n", fichero);

fclose(fichero);

}

Para definir una variable del tipo fichero, debemos usar la palabra FILE seguida del asterisco y a

continuación el nombre de dicha variable, el asterisco significa puntero y en este caso apunta a una

estructura que controla el objeto archivo.

A continuación para abrir el fichero se usa fopen, a la cual se le pasan como argumentos el nombre

del archivo y el tipo de acceso, w si es para escribir en el, r si es para leer, también en este caso

vemos que usa la letra t para aclarar que se un archivo de texto.

En el caso de un archivo abierto para escritura de no existir el mismo se creara y si ya existía se

borrara su contenido y creara vacío nuevamente. (mas adelante se vera como agregar información

sin borrar la existente)

Luego se usa fputs para escribir en el archivo, casi es forma similar como hacíamos para escribir en

pantalla con puts, solo que aquí habrá que especificar los saltos de linea por eso cada string que se

envía a fputs debe terminar en \n si queremos que la siguiente se agregue en la próxima linea, sino

lo hará a continuación, el otro argumento enviando a fputs debe ser el nombre de la variable tipo

fichero.

Al terminar de escribir en el lo cerraremos con fclose(), pasandole también como argumento el

nombre de la variable tipo fichero.

Bueno todo muy lindo, si lo corremos vemos que en la misma carpeta donde corre el ejecutable se

crea el archivo de texto llamado prueba.txt, si lo abrimos vemos su contenido.

Esto es una línea

Esto es otra y esto es continuación de la anterior

Page 122: c y Reversing

Veamos el código en IDA, renombrando convenientemente.

Pongamos un breakpoint al inicio de nuestra funcion para mirar un poco como funciona.

Si vemos las variables, la variable fichero es un dword ya que es un puntero y luego ya se

encuentra el stored ebp y return address no hay mas nada.

Ahora traceemos un poco.

Page 123: c y Reversing

Cuando llegamos a fopen si miramos en el stack cambiándolo con JMP TO ESP para que se

actualice, vemos los argumentos que se le pasan a la misma.

Un puntero a la string Prueba.txt y el otro un puntero a la string wt como en nuestro código fuente.

fichero = fopen("prueba.txt", "wt");

Vemos que el resultado de la llamada a fopen es guardado en la variable fichero, lo mismo vemos

en el IDA al volver de fopen guarda EAX en fichero.

mov [ebp+fichero], eax

Si vemos la definición de fopen

http://c.conclase.net/librerias/?ansifun=fopen

Vemos que dice que retorna un puntero

Valor de retorno:

La función fopen retorna un puntero al objeto controlando el stream. Si el proceso de

apertura no es realizado a cabo, entonces retorna un puntero nulo.

Bueno vamos viendo ya sabemos que este valor es un puntero, allí vemos en EAX su valor.

Podemos verlo tanto en una ventana de desensamblado, haciendo click derecho en EAX y eligiendo

JUMP IN A NEW WINDOW, como en una nueva ventana HEX, que se abre con VIEW-OPEN

Page 124: c y Reversing

SUBVIEWS-HEX DUMP y luego en ella se hace click derecho y se elije que se sincronice con

EAX.

Allí vemos dicha zona vacía, la misma corresponde a la sección data de la msvcrt.dll.

Si traceamos hasta pasar el fputs, vemos que se le pasan como argumentos la variable fichero y la

string a guardar en el archivo, vemos que la zona que tenia ceros ahora ya tiene valores.

Page 125: c y Reversing

El tema es que FILE * es un puntero a una estructura que controla el archivo, para el reversing no es

necesario estudiar como es la estructura pues solo se pasa el puntero a la misma y el sistema hace

todo el resto y el programa siempre maneja el puntero pasándoselo como argumento a las apis, pero

veremos por curiosidad un par de cositas sobre ella.

Cuando vamos a la ventana de estructuras y apretamos INS o INSERT.

De la lista desplegable elegimos la estructura FILE.

Quedara agregado

Page 126: c y Reversing

Si lo expandimos apretando +

Allí vemos la estructura que ocupa 0x20 bytes, así que vamos al listado donde esta el inicio de la

estructura (EAX apuntaba a ella al volver de fopen) y apretamos ALT mas Q.

Page 127: c y Reversing

Su buscamos en el archivo sdio.h que estará en nuestra maquina, ahí esta definida la estructura y

coincide aunque no aclara mucho que es cada campo.

Lo mas importante es esto:

Allí vemos el primer campo es un puntero, la dirección puede variar de maquina en maquina, pero

veamos a donde apunta.

Page 128: c y Reversing

Allí termina la string Esto es una linea así que este puntero, marca adonde termina el buffer

actualmente o sea a partir de aquí cuando agreguemos mas strings lo hará en esta dirección, el otro

puntero llamado base marca el inicio de todo el buffer o sea allí debe estar el inicio de la string Esto

es una linea

Allí esta inicio de buffer y fin del buffer el cual cambiara cuando agreguemos mas data pasando por

mas fputs.

Si seguimos traceando pasando por los restantes fputs veremos como el buffer va creciendo y las

siguientes strings se van apendeando a continuacion, cambiando el puntero de fin de buffer.

Page 129: c y Reversing

Así que la creación del fichero y el manejo de la memoria para escribir en el mismo, es manejado

por esta estructura, donde se almacenan los punteros a las strings que se guardan en un buffer de

sistema.

LECTURA DE UN ARCHIVO

Si queremos leer de un fichero, los pasos son similares, sólo que lo abriremos para lectura (el modo

de lectura tendrá una “r”, de “read”, en lugar de “w” que se usaba para escritura), y leeremos con

“fgets”:

Vemos un código que lee el archivo que creamos en el ejemplo anterior (asegúrense de que aun

exista jeje)

#include <stdio.h>

#include <string.h>

main(){

funcion();

getchar();

}

funcion()

{

Page 130: c y Reversing

FILE* fichero;

char nombre[80] = "prueba.txt";

char linea[81];

fichero = fopen(nombre, "rt");

if (fichero == NULL)

{

printf("No existe el fichero!\n");

exit(1);

}

fgets(linea, 80, fichero);

puts(linea);

fclose(fichero);

}

Vemos que en este caso creo dos arrays de caracteres uno de largo 80, al cual le asigno la string del

nombre del archivo, podría haberle pasado la string en forma directa como en el caso anterior,

funcionaria sin problemas, hubiera quedado así.

Volvamos al código original, vemos que crea un array de 80 caracteres para leer del archivo, en este

Page 131: c y Reversing

caso independientemente que el sistema mediante la estructura file guarde las strings leidas en un

buffer, nosotros debemos crear una variable propia donde copiara lo leído, pues debemos

almacenarlo en una variable manejable por mi.

#include <stdio.h>

#include <string.h>

main(){

funcion();

getchar();

}

funcion()

{

FILE* fichero;

char nombre[80] = "prueba.txt";

char linea[81];

fichero = fopen(nombre, "rt");

if (fichero == NULL)

{

printf("No existe el fichero!\n");

exit(1);

}

fgets(linea, 80, fichero);

puts(linea);

fclose(fichero);

}

Vemos que en este caso a fgets le debemos pasar un array de caracteres vacío que sera la variable

que contendrá lo leído, aquí la variable se llama linea, los otros dos argumentos son el tamaño de lo

leído y el puntero al fichero.

Si ejecutamos el programa vemos que fgets leerá hasta que encuentra un salto de linea y ahí

terminara aunque haya mas caracteres y aunque no lea los 80 que le pasamos como maximo.

También vemos que al abrir el fichero si es devuelto NULL o sea que no existe el archivo se sale

del programa mediante exit.

if (fichero == NULL)

{

printf("No existe el fichero!\n");

Page 132: c y Reversing

exit(1);

Bueno veamoslo en IDA y reverseemoslo.

Bueno aquí tenemos unas cuantas variables vamos a ir renombrando y despejando el camino,

empecemos con la var_C, apretando la tecla X vemos el momento en que se inicializa.

Vemos que se inicializa al volver de fopen por lo tanto var_C es nuestra variable fichero el puntero

Page 133: c y Reversing

a la estructura file, le ponemos el nombre.

Vemos que en las cuatro variables consecutivas var_68, var_64,var_60 y var_5e va leyendo el

nombre del archivo que esta en 403000 en adelante y lo va copiando a dichas variables, si vemos en

403000.

Si queremos ver la string marcamos la zona y hacemos undefine.

Page 134: c y Reversing

Así que esta inicializando mi variable array con el nombre del archivo Prueba.txt, y luego el resto

que no usa lo pone a cero usando memset, vemos que en EDX usando lea le pasa la dirección desde

donde tiene que llenar con ceros, luego el tamaño o sea 45h (69 decimal) y luego el valor con el

cual va a llenar o sea el cero, esto no es parte del código nuestro lo agrego el compilador para

inicializar correctamente el array con la string Prueba.txt y asegurarse que el resto quede vacío.

Así que nuestro array que habíamos llamado nombre

char nombre[80] = "prueba.txt";

comenzara en var_68, y las restantes variables que hay debajo hasta fichero son campos del mismo

con las cual trabaja, pero pertenecen al mismo array, así que undefinimos las variables que hay

debajo.

Page 135: c y Reversing

Vemos que hay lugar para 92, podemos tomarlo no hay problema aunque sea mas largo no afectara.

La otra variable que hay mas arriba llamada var_c8 es el otro array donde escribirá lo que lee del

archivo.

Si vemos donde la usa, vemos que con lea le pasa su dirección a fgets para llenar su contenido con

lo que lee del archivo, y luego le pasa la misma dirección a puts para imprimirlo por consola.

Page 136: c y Reversing

Así que ese es el otro array que en nuestro código habíamos llamado linea

char linea[81];

Así que creamos el array en dicha variable.

Nos quedaran las variables como en nuestro código fuente con los dos arrays y el puntero fichero.

Page 137: c y Reversing

Si vemos el código ahora, vemos que es mas fácil interpretar que esta copiando partes del nombre

del archivo cuando escribe en nombre, nombre+4, nombre+8 etc

Recordamos que había una comprobación de si fichero era NULL, si era así iba a exit, eso esta allí.

Page 138: c y Reversing

La variable fichero se comporta en forma similar que en el ejemplo anterior, al pasar por fopen, se

guarda el puntero a la estructura file, en ebp+ fichero.

Si vemos la dirección en EAX

si vemos esa zona abriendo una ventana HEX y sincronizando con EAX.

Vemos que aun esta vacía pero al igual que en el caso anterior a medida que vaya pasando por fgets

en este caso se ira llenado con los campos de la estructura los cuales se manejaran de la misma

forma que en el caso anterior, manteniendo punteros al inicio del buffer y al final temporal del

mismo.

Page 139: c y Reversing

Luego para terminar como vimos fgets leerá del archivo y guardara en var_c8 que ahora se llamara

linea y luego con puts mostrara lo que leyó del archivo imprimiendo en la consola.

El adjunto a continuacion es casi similar a este ultimo ejemplo y lee el mismo archivo Prueba.txt

me gustaría que vieran la diferencia con el que acabo de explicar.

Hasta la parte siguiente

Ricardo Narvaja

Page 140: c y Reversing

FUNCIONES-PARAMETROS-VARIABLES LOCALES Y GLOBALES – VALORES DE

RETORNO.

Hemos realizado algunos ejemplos hasta ahora con una función principal main que llamaba a otra

función que hacia todo el trabajo, pero en general los programas dividen el trabajo en muchas

funciones que hacen pequeñas partes cada una.

Del Curso de Cabanes, sobre la importancia de realizar varias funciones que realicen partes del

trabajo en vez de una sola muy complicada.

Veamos como ejemplo un programa como el que hemos realizado anteriormente:

# include <stdio.h>

main(){

funcion();

getchar();

}

funcion(){

int suma; /* Un entero que será la suma */

int numero0 = 200; /* Les damos valores */

int numero1 = 150;

int numero2 = 100;

int numero3 = -50;

int numero4 = 300;

suma = numero0 +numero1 + numero2 + numero3 + numero4;

printf("Su suma es %d", suma);

}

Vemos que se llama a funcion dentro de main y la misma esta declarada después, en DEV C++ esto

funciona perfectamente pero es bueno saber que otros compiladores no permiten hacer esto, y la

forma correcta para que funcione es siempre es declarar las funciones luego de los include..

Page 141: c y Reversing

# include <stdio.h>

funcion(){

int suma; /* Un entero que será la suma */

int numero0 = 200; /* Les damos valores */

int numero1 = 150;

int numero2 = 100;

int numero3 = -50;

int numero4 = 300;

suma = numero0 +numero1 + numero2 + numero3 + numero4;

printf("Su suma es %d", suma);

}

main(){

funcion();

getchar();

}

Vemos que funciona igual, así que dado que esta forma es mas genérica y aceptada por todos los

compiladores, la adoptaremos y definiremos al inicio después de los includes, todas las funciones

antes de usarlas.

Si compilan y ven en el IDA verán que quedara similar a como lo hacíamos antes, no cambia nada.

PARAMETROS DE UNA FUNCION

Si trabajamos en forma modular, cada funcion realizara pequeñas tareas, es muy probable que

Page 142: c y Reversing

debamos pasarle parametros o argumentos para que realice la misma, veamos este ejemplo, una

funcion que le pasamos un numero real y lo imprime en pantalla.

# include <stdio.h>

escribeNumeroReal(float n){

printf("%4.2f", n);

}

main(){

float x=5.1;

escribeNumeroReal(x);

getchar();

}

Allí vemos que al definir la funcion escribeNumeroReal entre paréntesis se colocara el parámetro

que recibirá definiendo al mismo tiempo el tipo de datos del mismo, en este caso float n, y cuando

se llame a la funcion de otra parte del programa se colocara entre paréntesis en este caso una

variable float que le pasamos o una constante que debe coincidir en tipo con la esperada por la

funcion, para que la misma lo acepte y lo imprima, el cual es el parámetro o argumento pasado a la

misma.

float x=5.1;

escribeNumeroReal(x);

Es necesario que se le puedan pasar parametros a las funciones, porque como ya vimos las variables

locales solo tienen sentido dentro de la funcion donde estan definidas, así que para poder pasar

valores entre funciones, se utilizan los parametros, y también lo que veremos luego, los valores de

retorno.

Al compilarlo y ejecutarlo vemos que imprime el valor del float, veamoslo en IDA.

Vemos el main y vemos lo resaltado en la imagen en verde que es lo único diferente a los main que

Page 143: c y Reversing

veníamos haciendo que solo llamaban a una funcion sin parametros.

# include <stdio.h>

escribeNumeroReal(float n){

printf("%4.2f", n);

}

main(){

float x=5.1;

escribeNumeroReal(x);

getchar();

}

Vemos que en el main ademas de las variables y argumentos que creaba el compilador siempre, hay

una variable local mas llamada var_4, vemos que se inicializa con el valor 40A33333h, vayamos al

menú de IDA,

Page 144: c y Reversing

Bueno redondeando es el float 5.1 en el código la variable float que se inicializa con dicho valor se

llama x, así que la renombramos.

# include <stdio.h>

escribeNumeroReal(float n){

printf("%4.2f", n);

}

main(){

float x=5.1;

escribeNumeroReal(x);

getchar();

}

Ahora esa variable x es una variable absolutamente local que tiene validez solo dentro del main, si

dentro de la funcion en 401290 quisiera usar el valor float 5.1, al llamar a la variable x, esta no

existirá y me dará error, para pasar valores a funciones como vimos se usan los parámetros, y según

el compilador los mismos se pushean antes de la llamada a la funcion, o como este compilador en

vez de pushear los guarda en el stack, es similar PUSH EAX a hacer MOV [ESP], EAX, aunque

a mi me gustan mas los compiladores que pushean los parametros para mi se ve mas claro.

PUSH EBX

PUSH EAX

call sub_401290

Uno ve los PUSH y sabe por la cantidad los parametros de una funcion a simple vista, mientras que

pasarlos con

mov [esp+4], eax

mov eax, [ebp+x]

mov [esp], eax

call sub_401290

Es menos visual para mi gusto, pero bueno entremos a la funcion.

Page 145: c y Reversing

Si hacemos doble click en arg_0, recordemos que la zona de variables locales era por encima del

stored_ebp y del return address, en este caso no hay variables locales por eso no hay nada por

encima.

Vemos el primer parametro arg_0, la zona de parametros estará siempre por DEBAJO del return

address, lo cual tiene cierta lógica pues si pusheo el parametro antes de entrar a la funcion, este se

guarda en el stack, y luego al entrar a la funcion se guarda encima del mismo el return address

(después de todo es un CALL y al entrar en el siempre se guarda el return address), y luego

comenzara la funcion generalmente con PUSH EBP, y ahí guarda el stored ebp, y luego declarara

las variables arriba del mismo.

Así que si hay un solo parametro IDA lo llamara arg_0 y sera el que se pushea justo antes de llamar

a la funcion, si hay mas parametros como en este otro caso:

PUSH EBX

PUSH EAX

call sub_401290

PUSH EBX es ejecutado primero por lo tanto su valor estará mas abajo en el stack que PUSH EAX

en este caso IDA llamara a este segundo parametro arg_4 y estará debajo de arg_0 y así

sucesivamente cuantos mas parametros tenga crecerá hacia abajo. (arg_8, arg_C)

Page 146: c y Reversing

Allí en el rectángulo rojo iría un segundo parametro arg_4 si lo hubiera, bueno volvamos a nuestra

funcion.

Vimos que los parametros se utilizan para pasar valores entre funciones, ya que las variables locales

no sirven para ello, así que al reversear es importante determinar, cual es el valor que se le esta

pasando y renombrarla de acuerdo a eso, en este caso sabemos que arg_0 tiene el mismo valor que

la variable x del main, así que podría renombrarla como x ya que es un parametro que tiene su

valor.

Es importante que el nuevo nombre aparezca en la definición de la funcion

Si no aparece y queda así

Page 147: c y Reversing

Hacemos click derecho SET FUNCION TYPE y lo arreglara tratando de usar los parametros

renombrados.

A la vez podemos renombrar la funcion a

y allí quedara mejor

Page 148: c y Reversing

Si hemos hecho aparecer el nuevo nombre del parametro en la definición de la funcion, al volver al

main vemos que nos muestra el nombre que le pusimos dentro al parametro, y que como vemos

coincide con el valor de la variable local x. De cualquier manera podría ponersele un nombre

aclaratorio diferente ya que la funcion puede llamarse desde distintos lugares del programa y con

distintos valores, que no siempre serán el de x, lo veremos en el proximo ejemplo.

VALOR DE RETORNO DE UNA FUNCION

Ya vimos los parametros de una funcion para poder pasarle valores, pero hay casos en que

necesitamos ademas de poder pasarle parametros, que la funcion nos devuelva algún resultado que

necesitamos usar en otra parte del programa, por ejemplo si tengo una funcion que le paso dos int y

la misma multiplica los dos valores, puedo necesitar que me devuelva ese resultado para usarlo.

Page 149: c y Reversing

Vemos que la funcion llamada cuadrado, tiene como parametro un int n y el valor de retorno que

se encuentra a continuacion de la palabra return sera el valor de n * n o sea el cuadrado de n.

Vemos que la funcion es llamada desde el main desde aquí.

numero= 5;

resultado = cuadrado(numero);

Se llama a la funcion cuadrado, pero se guarda el valor que devuelve o valor de retorno, por lo

cual cuando se define la funcion cuadrado se debe poner el tipo de valor que devolverá delante del

nombre de la misma.

int cuadrado ( int n ) {

return n*n;

}

Por lo tanto cuando se almacena la variable resultado debe ser declarada del mismo tipo o sea int

también.

#include <stdio.h>

int cuadrado ( int n ) {

return n*n;

}

main() {

int numero;

int resultado;

Page 150: c y Reversing

numero= 5;

resultado = cuadrado(numero);

printf("El cuadrado del numero es %d", resultado);

printf(" y el de 3 es %d", cuadrado(3));

getchar();

}

Así que deben coincidir el tipo del valor de retorno y el tipo de la variable local donde lo guardara.

Si una funcion esta especificada como tipo void significa que no devolverá ningún resultado.

Bueno volvamos a nuestro ejemplo

#include <stdio.h>

int cuadrado ( int n ) {

return n*n;

}

main() {

int numero;

int resultado;

numero= 5;

resultado = cuadrado(numero);

printf("El cuadrado del numero es %d", resultado);

printf(" y el de 3 es %d", cuadrado(3));

getchar();

}

Vemos que hay una segunda llamada a la funcion cuadrado, pasandole la constante 3 como

parametro, en este caso no se le pasa el valor de una variable sino directamente una constante, que

lógicamente debe coincidir con el tipo esperado por la funcion.

El valor devuelto por la misma en este caso no es almacenado sino usado directamente dentro del

printf que espera el valor de retorno para realizar el format string e imprimir el resultado del

cuadrado de 3.

Page 151: c y Reversing

Compilemos y veamoslo en IDA.

Vemos allí que la funcion es llamada dos veces, la primera es aquí y var_4 corresponde a la

variable numero que se inicializa con el valor 5 y se le pasa como parametro.

Renombrando

Vemos que le pasa como parametro el valor de numero que es 5, entremos a la funcion.

Page 152: c y Reversing

Vemos que el parametro único sera arg_0 la funcion lee este valor lo pasa a EAX y luego lo

multiplica por si mismo devolviendo en EAX el valor de retorno.

Podemos renombrar a la funcion como cuadrado.

Allí vemos el valor de retorno que devolverá en EAX.

Ahora el tema es el nombre del parametro ya que si le pongo el nombre numero pues en la primera

llamada tiene ese valor, en la segunda vez que la llame cuando tenga el valor 3, ya no coincidirá.

Page 153: c y Reversing

En estos casos es difícil aconsejar pues a veces nosotros al reversear necesitamos seguir un

parametro que vemos en una funcion, hacia las funciones padres y muchas veces conviene ponerle

un nombre e ir hacia atrás con el mismo nombre, para no perdernos y poder seguir identificando el

mismo valor que nos interesa.

Ahora propago el nombre haciendo click derecho – SET FUNION TYPE.

Page 154: c y Reversing

Quedara asi

Si vamos al main aparece el nombre que le pusimos, y en la primera llamada coinciden ya que toma

el valor de la variable numero, en la segunda bueno, igual me da la idea que al parametro numero

interno se le pasa el valor 3, ya se que el nombre del parametro interno es una ayuda y que no

necesariamente tiene que coincidir 100% con las variables locales del main, pero es bueno que

tenga el nombre alguna relación para orientarnos.

Hay reversers que no le ponen exactamente el mismo nombre sino que le ponen por ejemplo con

Page 155: c y Reversing

mayúsculas.

O por ahí alguna convención propia como Arg_numero o algo así.

Vemos que en la primera llamada el valor de retorno de la funcion que devuelve en EAX lo guarda

en var_8 asi que renombraremos la misma a resultado.

Luego ese valor guardado en resultado lo pasa como parametro de printf para imprimirlo.

Page 156: c y Reversing

En la segunda llamada vemos que no guarda el valor de retorno en ninguna variable directamente lo

guarda en el stack como parametro de printf directamente, que era lo que pasaba en nuestro código

fuente.

printf(" y el de 3 es %d", cuadrado(3));

La siguiente definición se la copiamos a Cabanes

Variables locales y variables globales

Las variables se pueden declarar dentro de un bloque (una función), y entonces sólo ese bloque las

conocerá, no se podrán usar desde ningún otro bloque del programa. Es lo que llamaremos

“variables locales”.

Por el contrario, si declaramos una variable al comienzo del programa, fuera de todos los “bloques”

de programa, será una “variable global”, a la que se podrá acceder desde cualquier parte.

En general, deberemos intentar que la mayor cantidad de variables posible sean locales (lo ideal

sería que todas lo fueran). Así hacemos que cada parte del programa trabaje con sus propios datos, y

ayudamos a evitar que un error en un trozo de programa pueda afectar al resto. La forma correcta de

pasar datos entre distintos trozos de programa es usando los parámetros de cada función y los

valores de retorno.

En el ejemplo anterior

#include <stdio.h>

int cuadrado ( int n ) {

return n*n;

}

main() {

int numero;

int resultado;

Page 157: c y Reversing

numero= 5;

resultado = cuadrado(numero);

printf("El cuadrado del numero es %d", resultado);

printf(" y el de 3 es %d", cuadrado(3));

getchar();

}

si lo cambiamos a

Vemos que numero y resultado son variables globales por eso son reconocidas en cualquier parte

del programa.

Allí vemos las dos variables globales, podemos renombrarlas igual, si hacemos doble click en el

nombre nos lleva a la sección bss adonde se guardan.

Page 158: c y Reversing

La funcion cuadrado no ha cambiado ya que se le pasan los valores como parametro.

Arreglándola

Page 159: c y Reversing

Volviendo al main el valor de retorno se guarda en resultado.

Page 160: c y Reversing

De cualquier forma dentro de cuadrado podríamos acceder a las variables globales directamente.

#include <stdio.h>

int numero;

int resultado;

void cuadrado(){

printf("El cuadrado del numero es %d",(numero*numero));

}

main() {

numero= 5;

cuadrado();

getchar();

}

En este ejemplo numero es una variable global y la funcion cuadrado no tiene parametros ni valor

de retorno, si lo compilo y veo en IDA.

Allí veo la variable global numero la renombro.

Page 161: c y Reversing

Veo que la funcion cuadrado no tiene parametros si entro en ella,

Veo que no tiene argumentos ni variables y que usa el valor de la variable global numero que esta

guardado en la sección bss.

También me muestra a la derecha la referencia desde donde es llamada dicha variable.

Page 162: c y Reversing

Bueno esto es todo por ahora con el tema funciones no les daré ejercicios aun, los dejare descansar

un poco.

Ricardo Narvaja

Page 163: c y Reversing

Punteros y gestión dinámica de memoria

¿Por qué usar estructuras dinámicas?

(Del curso de C de Cabanes)

Hasta ahora teníamos una serie de variables que declaramos al principio del programa o de cada

función. Estas variables, que reciben el nombre de ESTÁTICAS, tienen un tamaño asignado desde

el momento en que se crea el programa.

Este tipo de variables son sencillas de usar y rápidas... si sólo vamos a manejar estructuras de datos

que no cambien, pero resultan poco eficientes si tenemos estructuras cuyo tamaño no sea siempre el

mismo.

Es el caso de una agenda: tenemos una serie de fichas, e iremos añadiendo más. Si reservamos

espacio para 10, no podremos llegar a añadir la número 11, estamos limitando el máximo. Una

solución sería la de trabajar siempre en el disco: no tenemos límite en cuanto a número de fichas,

pero es muchísimo más lento.

Lo ideal sería aprovechar mejor la memoria que tenemos en el ordenador, para guardar en ella todas

las fichas o al menos todas aquellas que quepan en memoria.

Una solución “típica” (pero mala) es sobredimensionar: preparar una agenda contando con 1000

fichas, aunque supongamos que no vamos a pasar de 200. Esto tiene varios inconvenientes: se

desperdicia memoria, obliga a conocer bien los datos con los que vamos a trabajar, sigue pudiendo

verse sobrepasado, etc.

La solución suele ser crear estructuras DINÁMICAS, que puedan ir creciendo o disminuyendo

según nos interesen.

Todas estas estructuras tienen en común que, si se programan bien, pueden ir creciendo o

decreciendo según haga falta, al contrario que un array, que tiene su tamaño prefijado.

En todas ellas, lo que vamos haciendo es reservar un poco de memoria para cada nuevo elemento

que nos haga falta, y enlazarlo a los que ya teníamos. Cuando queramos borrar un elemento,

enlazamos el anterior a él con el posterior a él (para que no “se rompa” nuestra estructura) y

liberamos la memoria que estaba ocupando.

¿Qué son los punteros?

Un puntero no es más que una dirección de memoria. Lo que tiene de especial es que normalmente

un puntero tendrá un tipo de datos asociado: por ejemplo, un “puntero a entero” será una dirección

de memoria en la que habrá almacenado (o podremos almacenar) un número entero.

Vamos a ver qué símbolo usamos en C para designar los punteros:

int num; //donde "num" es un número entero

int *pos; //donde "pos" es un "puntero a entero" (dirección de memoria en la que podremos

guardar un entero)

Esta nomenclatura ya la habíamos utilizado aun sin saber que era eso de los punteros. Por ejemplo,

cuando queremos acceder a un fichero, hacemos

Page 164: c y Reversing

FILE* fichero;

Antes de entrar en más detalles, y para ver la diferencia entre trabajar con “arrays” o con punteros,

vamos a hacer dos programas que pidan varios números enteros al usuario y muestren su suma. El

primero empleará un “array” (una tabla, de tamaño predefinido) y el segundo empleará memoria

que reservaremos durante el funcionamiento del programa.

#include <stdio.h>

main() {

int datos[100]; /* Preparamos espacio para 100 numeros */

int cuantos; /* Preguntaremos cuantos desea introducir */

int i; /* Para bucles */

long suma=0; /* La suma, claro */

do {

printf("Cuantos numeros desea sumar? ");

scanf("%d", &cuantos);

if (cuantos>100) /* Solo puede ser 100 o menos */

printf("Demasiados. Solo se puede hasta 100.");

} while (cuantos>100); /* Si pide demasiado, no le dejamos */

/* Pedimos y almacenamos los datos */

for (i=0; i<cuantos; i++) {

printf("Introduzca el dato número %d: ", i+1);

scanf("%d", &datos[i]);

}

/* Calculamos la suma */

for (i=0; i<cuantos; i++)

suma += datos[i];

printf("Su suma es: %ld\n", suma);

}

Bueno el programa es similar a los que vimos anteriormente tiene un array de tamaño 100,

preparado para ingresar y almacenar datos, y dentro de un do-while que es igual a un while pero

con la condición al final.

El punto en que comienza a repetirse se indica con la orden “do”, así:

do

sentencia;

while (condición);

O sea que aquí se repetiría sentencia mientras la condición sea verdadera, volvamos a nuestro

ejemplo.

#include <stdio.h>

Page 165: c y Reversing

main() {

int datos[100]; /* Preparamos espacio para 100 numeros */

int cuantos; /* Preguntaremos cuantos desea introducir */

int i; /* Para bucles */

long suma=0; /* La suma, claro */

Aquí esta donde declaramos las variables, el array de enteros de tamaño 100 se llama datos, una

variable int llamada cuantos para guardar cuantos datos vamos a ingresar, un int llamado i como

contador para los ciclos, y un long llamado suma, que lo inicializamos a cero, para guardar el

resultado.

do {

printf("Cuantos numeros desea sumar? ");

scanf("%d", &cuantos);

if (cuantos>100) /* Solo puede ser 100 o menos */

printf("Demasiados. Solo se puede hasta 100.");

} while (cuantos>100); /* Si pide demasiado, no le dejamos */

Luego en este while se repetirá lo que esta resaltado mientras cuantos sea mayor que 100, o sea te

volverá a preguntar la cantidad eternamente, mientras pongas una cantidad mayor que 100, cuando

pongas una cantidad menor, te dejara seguir y salir del while.

for (i=0; i<cuantos; i++) {

printf("Introduzca el dato número %d: ", i+1);

scanf("%d", &datos[i]);

}

Luego tenemos un for donde el contador es i, el cual se inicializa en cero y se incrementa de uno en

uno hasta llegar al máximo que es cuantos, dentro de este for, se van introduciendo los campos del

array usando scanf, y guardándolo en los campos del array datos[ì].

for (i=0; i<cuantos; i++)

suma += datos[i];

printf("Su suma es: %ld\n", suma);

Luego otro for ira sumando todos los campos y guardando en suma y al final lo imprimirá.

Si compilamos y lo vemos en IDA, vemos allí donde marca la flecha roja donde comienza

realmente nuestro programa ya que lo anterior es agregado del compilador.

Page 166: c y Reversing

Dentro del ciclo

Vemos allí resaltada la variable cuantos que es donde mediante scanf se guarda la cantidad de datos

que vamos a ingresar, la renombramos y vemos que la compara si es menor o igual que 64h o sea

100 decimal, así que cambiamos el 64h por 100 decimal para que sea mas visible.

Page 167: c y Reversing

Voy coloreando los bloques que pertenecen al loop

Veo que dentro del mismo si es menor o igual que 100, saltea el printf que dice que son

“Demasiados...” siguiendo el camino de la flecha verde.

Page 168: c y Reversing

Luego ya comprueba la condición de salida volviendo a comprobar si cuantos es menor que 64h el

cual cambiamos a 100 decimal, esto nos hace ver que es un while pues no hay contador y saldrá

dependiendo de lo que tipee el usuario, por el camino de la flecha verde, sino va a un jmp que

vuelve al inicio del while.

A continuacion vemos un típico for con su contador que lo llamamos i en el código fuente, vemos

como lo inicia al principio, luego compara la condición de salida y al final lo incrementa.

Lo renombramos a i.

Page 169: c y Reversing

Vemos la condición de salida marcada con la flecha, cuando i sea mayor o igual que cuantos, saldrá

por allí, sino continuara por el siguiente bloque rosado.

La variable var_198 es el inicio de nuestro array debemos definirlo en las variables, aquí vemos

como pasa a EDX la dirección inicial y le va sumando i*4 ( obtenido con shl eax,2), para ir

salteando de cuatro en cuatro los campos del array y guardar consecutivamente en cada uno,

también deducimos que allí debe haber un array ya que al mirar la zona de las variables, vemos

mucho espacio vacío entre var_198 y lo siguiente definido.

Page 170: c y Reversing

Llega hasta el stored ebp y return address.

Page 171: c y Reversing

Así que no hay nada que se pueda pisar hacia abajo, y hacia arriba esta todo ya definido así que

apretamos asterisco, vemos que hay espacio para 102 enteros, como ya sabemos que hay un

máximo de 100 porque el mismo programa nos muestra en los mensajes que el máximo es 100, le

ponemos 100.

Quedara así.

Page 172: c y Reversing

Luego hay otro for que usa el mismo contador i para lo cual lo vuelve a inicializar a cero.

Vemos el for pintado de verde, al inicio la iniclializacion del contador i, luego la comparación de la

condición de salida si i es mayor o igual que cuantos, y al final el incremento del contador y vuelta

al inicio.

Page 173: c y Reversing

Vemos que dentro del for va tomando la var_198 que es nuestro array que debemos renombrar

como datos y va leyendo los valores de cada campo del mismo saltando de 4 en 4 usando i como

contador y multiplicándolo por 4 aquí.

mov edx, [ebp+eax*4+datos]

Así que en EDX tendremos el valor de cada campo al loopear y eso lo guarda en la variable

var_1a4 que sera la variable suma ya que con lea obtiene la dirección y le suma siempre EDX que

es valor del campo actual, a la salida tendremos allí la suma de todos valores de los campos del

array

Así que renombramos dicha variable var_1a4 como suma.

Page 174: c y Reversing

Al salir del for imprime el valor de suma.

Ahora veremos un ejemplo de un programa que es similar, vemos que el ejemplo anterior no es

eficiente, ya que se puede sumar a medida que se lee y ademas pongamosle que tengamos que

almacenar para realizar cálculos mas complejos, en realidad reservamos 100 dwords de memoria

para sumar por ahí dos o tres números lo cual es un desperdicio, veamos como se hace reservando

la memoria justa usando los nuevos conceptos.

#include <stdio.h>

#include <stdlib.h>

main() {

int* datos; /* Necesitaremos espacio para varios numeros */

int cuantos; /* Preguntaremos cuantos desea introducir */

int i; /* Para bucles */

long suma=0; /* La suma, claro */

do {

printf("Cuantos numeros desea sumar? ");

scanf("%d", &cuantos);

datos = (int *) malloc (cuantos * sizeof(int));

if (datos == NULL) /* Si no hay espacio, avisamos */

printf("No caben tantos datos en memoria.");

} while (datos == NULL); /* Si pide demasiado, no le dejamos */

/* Pedimos y almacenamos los datos */

for (i=0; i<cuantos; i++) {

printf("Introduzca el dato número %d: ", i+1);

scanf("%d", datos+i);

}

/* Calculamos la suma */

for (i=0; i<cuantos; i++)

suma += *(datos+i);

printf("Su suma es: %ld\n", suma);

free(datos);

Page 175: c y Reversing

}

Si lo ejecutamos vemos que funciona en forma similar al anterior.

Ahora veamos el cambio en el código fuente.

main() {

int* datos; /* Necesitaremos espacio para varios numeros */

int cuantos; /* Preguntaremos cuantos desea introducir */

int i; /* Para bucles */

long suma=0; /* La suma, claro */

Cuando declaramos int* datos; en vez de int datos[100]; La diferencia es que ahora no

reservamos 100 dwords fijos en la memoria sino que tenemos una variable que es un puntero a una

zona que habrá ints pero no sabemos cuantos, los mismos podrán ser tantos como la memoria de la

maquina nos lo permita.

Así que datos ahora es una variable puntero a int, y por ahora no esta inicializada cuando lo

hagamos guardara una dirección o sea un puntero al int o a los ints., se debe ver bien esta diferencia

no es lo mismo que una variable guarde datos que guarde un puntero adonde estarán los datos.

do {

printf("Cuantos numeros desea sumar? ");

scanf("%d", &cuantos);

datos = (int *) malloc (cuantos * sizeof(int));

if (datos == NULL) /* Si no hay espacio, avisamos */

printf("No caben tantos datos en memoria.");

} while (datos == NULL); /* Si pide demasiado, no le dejamos */

Aquí esta la clave del asunto, al igual que antes nos pregunta cuantos números vamos a sumar y lo

guarda en la variable cuantos.

Ahora realizamos la cuenta de cuanto espacio necesitaremos en la memoria según la cantidad de

enteros a sumar, la misma sera la cantidad de enteros que esta en cuantos por el tamaño que ocupa

en bytes cada int, lo que se halla haciendo sizeof(int) lo que nos devuelve 4.

cuantos * sizeof(int)

Page 176: c y Reversing

Así que esto nos dará el tamaño en bytes que necesitamos en memoria, el cual se pasa a la funcion

malloc que reserva dicho tamaño y nos devuelve un puntero al mismo.

datos = (int *) malloc (cuantos * sizeof(int));

Como datos es un int * conviene castear el resultado de malloc ya que realmente devuelve un

puntero a la memoria que acaba de reservar, pero es un numero, así que como vimos en la parte de

casting forzamos a que el programa lo interprete como int * de forma de que no haya problemas de

asignación en datos y nos de error al compilar.

El resto del programa es similar:

/* Pedimos y almacenamos los datos */

for (i=0; i<cuantos; i++) {

printf("Introduzca el dato número %d: ", i+1);

scanf("%d", datos+i);

}

Page 177: c y Reversing

/* Calculamos la suma */

for (i=0; i<cuantos; i++)

suma += *(datos+i);

Vemos primero que nada, que en este caso la funcion scanf no necesita & delante de donde

guardara los datos, sabemos que ello hacia que se halle la dirección a la variable donde se

guardaran los datos, en este caso la misma ya es un puntero asi que no necesita &.

Lo siguiente diferencia es que antes en un array recorríamos los campos del mismo como datos[0],

datos[1] y eso iba saltando entre los mismos, aquí sera *datos, el segundo *(datos+1), el tercero

será *(datos+2) y así en adelante. Por eso, donde antes hacíamos suma += datos[i]; ahora usamos

suma += *(datos+i); el significado es que siempre sera un puntero e ira recorriendo la memoria

apuntando al dato actual, sumándole valores constantes al puntero inicial, con lo cual se obtiene un

nuevo puntero.

También aparece una llamada a free que es lo contrario de malloc, liberara la zona reservada de

memoria para que se pueda disponer de ella, ya que no la usamos mas.

Vemos el programa en el IDA a ver las diferencias.

Page 178: c y Reversing

Vemos que una vez que guarda en cuantos la cantidad de números a sumar, lo multiplica por 4 para

hallar el tamaño de bytes a reservar en la memoria, y eso lo pasa como argumento a malloc que nos

devolverá el puntero a la zona reservada de dicho tamaño, el puntero lo guarda en var_4 que es

nuestro puntero int * llamado datos así que lo renombramos, aquí no hay desperdicio de memoria

pues el tamaño de un puntero es un dword que es lo que vemos que reservo para guardar ese valor.

Page 179: c y Reversing

Aquí vemos como maneja el puntero cuando guarda los datos dentro del for

La variable i sera igual que antes, el contador, lo multiplica por 4 y lo suma al puntero datos, de esa

forma cuando i valga 0, el puntero datos apuntara al inicio de la zona reservada, luego cuando i

valga 1, sera por lo tanto i*4, y eso se le sumara al puntero al inicio con lo cual obtendremos un

nuevo puntero al segundo int ya que vamos barriendo de 4 en cuatro, sumándole al puntero original,

para que quede mas claro lo debuggearemos.

Page 180: c y Reversing

Arranco el programa y me pide cuanto números sumare le pongo como ejemplo 4, luego parara en

el llamado a malloc.

El 4 que ingrese lo guarda en cuantos y lo multiplica por 4 para hallar el tamaño a reservar que sera

16, o sea 10 hexa como vemos en EAX y en el stack si hacemos JMP TO ESP, ese sera el tamaño de

la memoria a reservar, pasemos el call malloc con f8.

Page 181: c y Reversing

Allí vemos que en EAX nos devuelve un puntero adonde reservo la memoria, la dirección puede

cambiar de maquina en maquina pero si miro dicha zona.

Page 182: c y Reversing

Allí hay lugar para 4 dwords y esta relleno con 0D F0 AD BA (bad food) porque el programa esta

siendo debuggeado para que veamos bien la zona reservada, sino serian ceros, jeje y donde termina

la zona reservada vemos ABABABAB (también solo en modo debug).

Si traceamos y llegamos hasta dentro del for aquí

Vemos que i vale 0 y se mueve a EAX.

Luego con el SHL EAX, 2 lo multiplica por 4, el resultado sera cero.

Page 183: c y Reversing

Luego toma nuestro puntero datos y le suma el resultado obtenido o sea cero, recordemos que es

un int * asi que al sumarle una constante dará un puntero en este caso al sumarle cero dará el mismo

valor que apunta al primer int de la zona de memoria reservada donde guardara lo que tipeamos,

apretamos f8 hasta que pasamos el scanf.

Allí guardara el primer valor 21 hexa que es 33 decimal.

Si repetimos traceando con f8 y llegamos al mismo bloque nuevamente.

Page 184: c y Reversing

Ahora el contador i vale 1, lo multiplica por 4 con el SHL lo que dara 4.

Lo suma al puntero que tenemos en datos.

Quedando en EAX un nuevo puntero al segundo int a guardar.

Page 185: c y Reversing

Si pasamos el scanf con f8 veremos como guarda allí el segundo valor.

Así que lo que se indexa aquí es el puntero, y se le van sumando en este caso de 4 en 4 para ir

guardando en la zona reservada en forma correcta, lo mismo cuando lo lea, tomara el puntero a

dicha zona y le ira sumando cuatro para leer los valores guardados, si ponemos un BP en el call a

free al final luego de guardar los cuatro valores y realizar la suma.

Page 186: c y Reversing

Al llegar al free se le pasa como argumento el puntero a la zona reservada

Si lo pasamos con f8 vemos que dicha zona cambio y ya no tiene nuestros datos sino punteros, eso

se estudiara mas adelante el mecanismo que tiene el sistema para saber como una zona esta libre

para reservar o no.

Page 187: c y Reversing

Allí hay un ejemplo de punteros a resolver lo único molesto es que usa floats y eso a veces es un

poco molesto de ver en IDA pero bueno es así la vida jeje.

Hasta la próxima parte

Ricnar

Page 188: c y Reversing

MAS SOBRE PUNTEROS-INCREMENTAR PUNTEROS

Tratemos de reforzar bien el concepto de punteros aunque seamos redundantes.

Es sencillo entender que si declaramos una variable int

int pepe =5

y hacemos

pepe ++

el valor de pepe valdrá 6, eso esta bien claro, el código completo

#include <stdio.h>

#include <stdlib.h>

main() {

int pepe =5;

pepe ++;

printf ("%d",pepe);

getchar();

}

Si lo corremos muestra que pepe vale 6

Ahora veamos el siguiente código:

#include <stdio.h>

#include <stdlib.h>

main() {

int * punt;

punt = (int *) malloc (sizeof(int));

*punt = 3;

Page 189: c y Reversing

printf ("%x \n",punt);

punt ++;

printf ("%x\n",punt);

getchar();

}

declaramos una variable llamada punt que es un puntero

int * punt;

Lo inicializamos, por supuesto tendrá una dirección de memoria que devuelve el malloc

punt = (int *) malloc (sizeof(int));

guardamos un 3 en su contenido o sea en la dirección adonde apunta

*punt = 3;

Y luego incrementamos punt

punt ++;

Nosotros hemos incrementado el valor de “punt”. Como “punt” es un puntero, estamos

modificando una dirección de memoria. Por ejemplo, si “punt” se refería a la posición de memoria

número 10.000 de nuestro ordenador, ahora ya no es así, ahora es otra posición de memoria distinta.

Como ya sabemos, el espacio que ocupa una variable en C depende del sistema operativo. Así, en

un sistema operativo de 32 bits, un “int” ocuparía 4 bytes, de modo que la operación

punt++;

haría que pasáramos de mirar la posición 10.000 a la 10.004.

Por si alguien tiene dudas ejecutemoslo:

Page 190: c y Reversing

Como le puse %x me mostrara el valor hexadecimal de punt, vemos que en mi maquina (en otras

variara), punt vale 0x3e2480 y luego de incrementarlo valdrá 0x3e2484 ya que se incrementa de

cuatro en cuatro para apuntar a un segundo int, que no hemos inicializado.

Si quisiéramos incrementar el 3 que guardamos en el contenido de punt deberíamos hacer.

(*punt) ++;

En este codigo

#include <stdio.h>

#include <stdlib.h>

main() {

int * punt;

punt = (int *) malloc (sizeof(int));

*punt = 3;

printf ("%x \n",punt);

printf ("%x\n",*punt);

(*punt) ++;

printf ("%x\n",*punt);

Page 191: c y Reversing

getchar();

}

Vemos que primero imprimo el valor del puntero allí resaltado en verde, luego imprimo su

contenido y luego lo incremento de 3 a 4 y luego lo imprimo nuevamente.

printf ("%x \n",punt);

printf ("%x\n",*punt);

(*punt) ++;

printf ("%x\n",*punt);

Allí vemos que se incremento de 3 a 4.

La conclusión de todo esto es que para incrementar punteros usaremos

punt ++

y para incrementar su contenido

(* punt) ++

Lo mismo que pasa asignar valores a un puntero se hará directamente en punt y para asignar en su

contenido se hará en *punt.

Page 192: c y Reversing

Asignando valores a punt

punt = (int *) malloc (sizeof(int));

Asignando valores a su contenido

*punt = 3;

Es muy importante que esto quede claro, pues es la piedra fundamental de todo lo que sigue, por si

alguno le quedo alguna duda veamoslo en IDA.

Allí vemos el código luego de lo agregado por el compilador

Vemos que llama a malloc con el size 4 y que el resultado lo devuelve en var_4 que sera nuestro

punt, así que lo renombramos, sabemos que punt es un puntero o sea que sera una dirección de

memoria, si ponemos un breakpoint allí.

Page 193: c y Reversing

Vemos que EAX tiene el valor de punt que en mi maquina es 0x3e3ce0 y que como lo arrancamos

en un debugger y le pedimos 4 bytes en la memoria el contenido de 0x3e3ce0 tendrá BAAD FOOD

jeje como dijimos en las partes anteriores y ABABABAB donde termina la zona reservada.

La cuestión es que luego de guardar EAX en punt lo que corresponde a nuestro código fuente

punt = (int *) malloc (sizeof(int));

Lo vuelve a mover a EAX y guarda en su contenido, donde hace

004012CC mov dword ptr [eax], 3

esta realizando lo que en nuestro código fuente era

*punt = 3;

Y modificando el contenido de punt, si pasamos con f8 dicha instrucción.

Page 194: c y Reversing

Vemos que guardo el 3.

Luego vemos los tres printf

En el primero lee el valor de punt y lo imprime.

.text:004012D2 mov eax, [ebp+punt]

.text:004012D5 mov [esp+4], eax

.text:004012D9 mov dword ptr [esp], offset asc_403000 ; "%x \n"

.text:004012E0 call printf

Si traceamos con f8 y lo pasamos al printf vemos en la consola el valor de punt.

Page 195: c y Reversing

El siguiente printf debería imprimir el contenido de punt o sea *punt.

.text:004012E5 mov eax, [ebp+punt]

.text:004012E8 mov eax, [eax]

.text:004012EA mov [esp+4], eax

.text:004012EE mov dword ptr [esp], offset asc_403005 ; "%x\n"

.text:004012F5 call printf

Allí vemos la instrucción donde obtiene el contenido de punt o sea el valor 3 si pasamos el printf

con f8, vemos en la consola el valor de *punt.

En el ultimo printf incrementábamos *punt de 3 a 4 y lo imprimíamos veamos.

.text:004012FA mov eax, [ebp+punt]

.text:004012FD inc dword ptr [eax

En EAX estará punt y incrementara su contenido mediante la instrucción en verde, pasando el

mismo a 4 si pasamos con f8 el INC.

Page 196: c y Reversing

Que fue el equivalente en nuestro código de

(*punt) ++;

Luego mueve el 4 a EAX y lo imprime.

.text:00401302 mov eax, [eax]

.text:00401304 mov [esp+4], eax

.text:00401308 mov dword ptr [esp], offset asc_403005 ; "%x\n"

.text:0040130F call printf.

Al llegar a este punto muchos que trabajaron en bajo nivel siempre se preguntan, que diferencia hay

entre & y *, ya que ambos son direcciones de memoria, y realmente la diferencia es mas de C que

de bajo nivel, pero si entendimos todo hasta aquí vemos que & es usando para pasar direcciones de

memoria de variables como argumento a otras funciones donde dicha variable no esta definida, o

para incrementar el valor de dicha variable, es similar al LEA aunque en la practica nos da una

dirección de memoria cuyo contenido es una variable, en cambio * es también una dirección de

memoria pero en esta caso la variable puntero contendrá una dirección de memoria, en cuyo

contenido podremos trabajar, la diferencia es muy fina y es posible que todavía haya dudas,

veremos como usar ambas a la vez en el siguiente ejemplo.

Page 197: c y Reversing

#include <stdio.h>

void duplica(int *x) {

*x = *x * 2;

}

main() {

int n = 5;

printf("n vale %d\n", n);

duplica(&n);

printf("Ahora n vale %d\n", n);

getchar();

}

Vemos que la funcion duplica tiene definido que recibe un puntero (int *) y busca su contenido y lo

multiplica por dos, modificando el contenido allí mismo sin devolver el valor.

void duplica(int *x) {

*x = *x * 2;

}

En el main cuando se llama a la funcion duplica se pasa una dirección de memoria de la variable n

que vale 5, por ejemplo le pasa 0x10000, al pasarle la dirección de memoria a duplica, esta la toma,

busca el contenido de 0x10000, que es 5 y lo multiplica por dos y lo modifica allí mismo,

guardando en 0x10000 el 10 decimal.

main() {

int n = 5;

printf("n vale %d\n", n);

duplica(&n);

printf("Ahora n vale %d\n", n);

Por eso en el printf aparece n con un nuevo valor que no se cambio en la funcion main, este es un

ejemplo de lo que decíamos en las primeras partes del curso, que para modificar el valor de una

variable local, ingresando en otra funcion, donde dicha variable no esta definida, la única forma es

pasarle con & o LEA su dirección y que luego dentro de la misma se trabaje en el contenido de

dicha variable, lo cual se realiza por ejemplo si la dirección esta en EAX usando [EAX] para

modificar su contenido y eso es lo que hace duplica usando * para modificar el contenido de una

dirección.

O sea este es el ejemplo clásico de como modificar el valor de una variable local usando & o LEA,

dentro de otra funcion y por eso cuando estamos reverseando una funcion, y vemos que en IDA al

apretar X en una variable local, si ademas de la asignaciones directas, hay un LEA, enseguida nos

fijamos en esa instrucción, pues allí puede cambiar de valor de dicha variable al entrar en otra

subfuncion, o al modificar el contenido de la memoria.

Page 198: c y Reversing

Si lo vemos en IDA.

Vemos que n vale 5 y usa printf para imprimir su valor.

Luego le pasa la dirección de memoria de la variable n, si ponemos un breakpoint allí, vemos que

en EAX estará la misma, la cual se pasa la la funcion duplica, entramos en ella.

Page 199: c y Reversing

Si traceamos vemos que lógicamente arg_0 sera la dirección de memoria de n, aquí n no tiene

significado pues es una variable local, pero si podemos trabajar con su dirección de memoria y

cambiar su contenido.

Page 200: c y Reversing

Aquí el argumento direccion_de_n es un puntero, vemos que lo mueve a EDX y a EAX.

Luego mueve a EAX el contenido de dicha dirección de memoria que sera el valor de n que estará

en el contenido de EAX.

Page 201: c y Reversing

Luego con add EAX,EAX duplica el valor. y lo vuelve a guardar en el contenido de EDX o sea

reemplaza el 5 por el 10.

Al ejecutar el mov [EDX],EAX cambiara a 10 decimal o sea 0a hexa.

Page 202: c y Reversing

Y allí volvemos a main.

Ahora aquí movemos el valor de n a EAX pero este ya no vale 5 sino vale 0a hexa jeje, dentro de

duplica se cambio su valor.

Page 203: c y Reversing

Y si por eso cuando se pasa como argumento de una funcion una dirección de memoria de una

variable local, que se hallo con LEA, podemos esperar que dentro de dicha funcion se cambie el

valor de la variable local.

Bueno no quiero seguir mas hasta que no digieran bien esto les recomiendo que repasen bien, los

ejemplos de punteros pues se va a venir mas difícil si no entendieron bien esto.

Hasta la parte siguiente, adjunto un ejemplo para reversear para que practiquen

Ricardo Narvaja

Page 204: c y Reversing

SEGUIMOS CON MAS SOBRE PUNTEROS

Cuando declaramos un dato como array, existe una similitud con un puntero, lo veremos en el

ejemplo a continuacion:

#include <stdio.h>

main() {

int datos[10];

datos[0]=5 ;

printf ("%d ", datos[0]);

getchar();

}

Sabemos que para asignar valores a los campos del array normalmente usamos datos[x] , como en

el ejemplo datos[0], pero en si que pasa si imprimimos el valor de la variable datos solo sin

subindice, realmente datos tiene algún valor?

#include <stdio.h>

main() {

int datos[10];

int i;

printf ("%x ", datos);

getchar();

}

Vemos que cuando declaramos un array, al utilizar el nombre solo sin subindices contiene la

dirección de memoria donde se inicia el array, al cual se le pueden asignar y leer valores como si

Page 205: c y Reversing

fuera una variable puntero.

#include <stdio.h>

main() {

int datos[10];

int i;

printf ("%x\n", datos);

for (i=0; i<10; i++)

*(datos+i) = i*2;

for (i=0; i<10; i++)

printf ("%d ", *(datos+i));

getchar();

}

Vemos que le asignamos valores a sus campos, tal cual datos fuera puntero, y los lee de la misma

forma, la asignación directa también puede realizarse como puntero.

#include <stdio.h>

main() {

int datos[10];

int i;

printf ("%x\n", datos);

*(datos)= 20;

printf ("%d ", *(datos));

getchar();

}

Page 206: c y Reversing

Aquí lo asignamos como puntero y lo leemos en la forma tradicional

#include <stdio.h>

main() {

int datos[10];

int i;

printf ("%x\n", datos);

*(datos)= 20;

printf ("%d ", *(datos));

*(datos+1)= 40;

printf ("%d ", datos[1]);

getchar();

}

Punteros y estructuras (choreado de Cabanes)

Igual que creamos punteros a cualquier tipo de datos básico, le reservamos memoria con “malloc”

cuando necesitamos usarlo y lo liberamos con “free” cuando terminamos de utilizarlo, lo mismo

podemos hacer si se trata de un tipo de datos no tan sencillo, como un “struct”.

Eso sí, la forma de acceder a los campos del struct cambiará ligeramente. Para un dato que sea un

número entero, ya sabemos que lo declararíamos con int *n y cambiaríamos su valor haciendo algo

Page 207: c y Reversing

como *n=2, de modo que para un struct podríamos esperar que se hiciera algo como

*persona.edad = 20. Pero esa no es la sintaxis correcta: deberemos utilizar el nombre de la variable

y el del campo, con una flecha (->) entremedio, así: persona->edad = 20. Vamos a verlo con un par

de ejemplos para que se aclare bien:

#include <stdio.h>

main() {

/* Primero definimos nuestro tipo de datos */

struct datosPersona {

char nombre[30];

char email[25];

int edad;

};

/* La primera persona será estática */

struct datosPersona persona1;

/* Damos valores a la persona estática */

strcpy(persona1.nombre, "Juan");

strcpy(persona1.email, "[email protected]");

persona1.edad = 20;

printf("Primera persona: %s, %s, con edad %d\n",

persona1.nombre, persona1.email, persona1.edad);

getchar();

}

Page 208: c y Reversing

Este es el caso clásico que ya habíamos visto en partes anteriores, la persona, esta instanciada como

variable estática en el stack, veamoslo en IDA así vemos la diferencia con el caso dinámico que

veremos luego.

Vemos la linea roja que separa lo agregado por el compilador, asimismo la variable var_4c es

creada por el mismo, la primera variable nuestra es la var_48

Allí vemos cuando inicializa la variable var_48, en celeste vemos que copia la string “Juan”, luego

vemos en amarillo que se suma 1eh desde var_48 y copia otra string con el mail, esto nos podría

hacer pensar que es un array de strings, pero luego vemos la var_10 a la cual se le asigna el valor

14, y pensamos que todo esto es una estructura ya que si vemos las variables.

Page 209: c y Reversing

Ya ver espacio vacío no definido entre variables, es sospechoso de estructura o array estatico, una

asignación de un valor en un espacio intermedio no definido, entre las variables como aquí en

var_48 + 1e, es también sospechoso de array o estructura, pero al seguir hacia abajo y ver que

donde asigna el valor a la var_10 esta consecutiva al espacio no definido y es de un tipo diferente

podemos concluir que no es un array pues tiene campos de diferentes tipos, así que estamos en

presencia de una estructura.

Así que vayamos a la pestaña de creación de estructuras, sabemos que para escribir la segunda

string le suma 1eh así que el largo de la primera sera 30, creamos la estructura y agregamos con

asterisco un array.

Page 210: c y Reversing

Como tomo como char o sea un byte el inicio, luego al colocar 30 bytes de largo, pues creara el

array de caracteres correspondiente, le pongo el nombre correcto.

Vuelvo a las variables y aprieto asterisco en la primera solo para que me diga el espacio en bytes

que hay hasta la var_10, no creo nada.

Page 211: c y Reversing

Veo que hay 56 bytes, así que si le resto los 30 del primer campo, quedaran 26 bytes, así que

cancelo la ventana y vuelvo adonde estan las estructuras y creo un segundo campo, que sea un array

de 26 hexa de largo.

Page 212: c y Reversing

Vemos que en el mismo nombre nos indica que esta en la posición 1eh desde el inicio, así que lo

creamos parece que vamos bien.

Ahora agregamos el tercer campo que es un int que debería coincidir con la var_10.

Allí tenemos armada la estructura así que ahora asignemos a la variable var_48, apretando ALT mas

Q sobre la misma.

Nos queda renombrar la estructura pongamosle el nombre original ya que lo sabemos.

Page 213: c y Reversing

Así que ahora var_48 es del tipo datosPersona, podemos renombrar la variable var_48 ya que es

una instancia de datosPersona y en nuestro código la llamábamos persona1, así que lo hacemos.

Renombrada

Por supuesto persona1 esta creada en el stack en forma estática usando el lugar que

predeterminamos en el mismo para cada campo.

Page 214: c y Reversing

Pongamos un breakpoint allí para ver si coincide al ejecutar el programa.

Bueno vemos que va a pasar como argumento de strcpy el puntero a donde se encuentra la string

Juan, en este caso 403000.

Luego lee la dirección de persona1 con lea para pasársela como argumento a strcpy la cual copiara

la misma allí, pasemos el strcpy con f8.

Si hago click en persona1

Page 215: c y Reversing

Veo el stack, la palabra Juan y el cero final de la string

Para aclarar asigno allí a 22ff30 la estructura que cree con ALT mas Q.

Page 216: c y Reversing

Ugh quedo feo veamos como arreglarlo aunque el hecho de que tenga basura entre medio de las

strings complica a que el IDA lo reconozca bien, pero bueno yendo a la definición de la estructura y

poniendo visible el menú.

Al marcar los campos de strings y apretar allí se pone verde y muestra char, aunque todavía no

queda bien.

Queda un poco mejor pero no como string, ahora un truco medio de la galera si originalmente en

Page 217: c y Reversing

vez de asterisco encima de nombre apretamos la A.

y aceptamos vemos que cambio el tipo a string C

Hago lo mismo con la otra.

Bueno al menos vemos las strings, el tema de que no las agrupa debe ser por la basura intermedia,

pero al menos vemos que coinciden, donde empieza email esta la string del mismo y donde empieza

edad esta el 14h.

Page 218: c y Reversing

Vemos que luego saca las direcciones del primer campo nombre y le suma 1eh para obtener la

dirección del segundo campo email y los imprime, ahora veremos el mismo caso pero dinamico.

#include <stdio.h>

main() {

/* Primero definimos nuestro tipo de datos */

struct datosPersona {

char nombre[30];

char email[25];

int edad;

};

/* La segunda persona será dinamica */

struct datosPersona *persona2;

/* Ahora a la dinámica */

persona2 = (struct datosPersona*)

malloc (sizeof(struct datosPersona));

strcpy(persona2->nombre, "Pedro");

strcpy(persona2->email, "[email protected]");

persona2->edad = 21;

/* Mostramos los datos y liberamos la memoria */

printf("Segunda persona: %s, %s, con edad %d\n",

Page 219: c y Reversing

persona2->nombre, persona2->email, persona2->edad);

free(persona2);

getchar();

}

Vemos que la estructura es la misma que antes, pero en vez de instanciarla en forma estática, lo

hacemos en forma dinámica, ahora persona2 sera un puntero a la estructura.

struct datosPersona *persona2;

Vemos como reserva el espacio necesario ya que sizeof le devuelve al compilador el tamaño de la

estructura, así que eso sera una constante, por supuesto malloc devolverá un puntero a la zona

donde reservo dicho espacio, luego para que no haya problemas al compilar castea el tipo a (struct

datosPersona*) como puntero a esa estructura.

persona2 = (struct datosPersona*)

malloc (sizeof(struct datosPersona));

Así que tenemos nuestro puntero en persona2, y el espacio necesario reservado al que apunta,

como habíamos visto en el caso de las estructuras dinámicas se debe usar la flecha para acceder a

los campos.

strcpy(persona2->nombre, "Pedro");

strcpy(persona2->email, "[email protected]");

persona2->edad = 21;

Luego imprime la salida también usando la flecha para acceder a los campos y luego cuando

termina libera la memoria reservada.

printf("Segunda persona: %s, %s, con edad %d\n",

persona2->nombre, persona2->email, persona2->edad);

free(persona2);

Funciona perfectamente

Veamos este nuevo ejemplo en IDA

Page 220: c y Reversing

Vemos donde empieza nuestro código debajo de la linea, lo primero que observamos es que no se

esta reservando lugar en el stack para la estructura como antes, solo una variable var_4 que es un

dword es usada, la otra var_8 la usa el compilador.

Vemos que no hay espacio no definido, las variables estan consecutivas, y la var_4 es un dword y a

continuacion ya se encuentra el stored_ebp, así que no hay lugar vacío para arrays ni estructuras

estáticas aquí, solo nuestra var_4 que ocupa 4 bytes y nada mas.

Lo primero que hace es llamar a malloc reservando el espacio 3ch que es el tamaño de nuestra

estructura, el puntero lo guarda en la variable que podemos renombrar como persona2.

Page 221: c y Reversing

Luego trabaja todo con punteros así que aquí no hay que hacer lea ni nada, le pasamos el puntero

persona2 a strcpy, IDA reconoce que es un puntero y por eso nos muestra char * llenamos el

campo nombre.

Luego a dicho puntero le suma 1eh obteniendo un puntero al segundo campo email de la estructura

también aquí IDA nos muestra que el argumento es un char *.

mov eax, [ebp+persona2]

mov dword ptr [eax+38h], 15h

Luego lee el puntero a persona2 nuevamente le suma 38h y allí asigna el valor 15 al campo edad.

Aquí no podemos en el análisis estático, asignar nuestra estructura a nada, pues no esta en el stack y

es dinámica o sea creada el correr el programa, lo debuggearemos para ver si quedo todo bien.

Al pasar malloc EAX tiene el valor del puntero en mi maquina 0x3e3d78, allí reservo 3ch de

espacio veamos en el DUMP hagamos click derecho SINCRONIZE WITH EAX.

Page 222: c y Reversing

Allí esta, traceemos con f8 y vayamos copiando, vemos como llena los campos de la estructura con

las strings Pedro, el mail y el dword 15.

Si vemos la estructura en el desensamblado

Page 223: c y Reversing

Bueno no me deja me da error, pero a no asustarse que yo estoy temblando seguro que como no la

guardamos no nos deja, pero bueno se crea en un minuto de nuevo ya sabemos que si el campo es

una string apretamos A en vez de asterisco.

Creamos la estructura nuevamente, usamos A en vez de asterisco en los campos que serán strings.

Page 224: c y Reversing

Vemos que los datos corresponden aunque quizás por el mismo motivo que cuando es estática,

demasiada basura intermedia, no nos crea el formato mas cómodo, pero vemos bien que esta todo

correcto.

Luego primero le pasa como argumento el valor de la edad, que obtiene a partir del puntero

persona2 al cual le suma 38h y obtiene un puntero al campo edad y al cual le halla el contenido 15

y lo guarda en el stack, para los otros campos ya que son strings solo es necesario el puntero al

inicio de la misma así que le pasa puntero2 para el nombre y le suma 1eh para apuntar al mail,

luego hace free de persona2 para liberar la memoria reservada.

Hay un ejercicio para solucionar casi igual a ambos de esta parte pero juntos.

Hasta la parte siguiente

Ricardo Narvaja

Page 225: c y Reversing

LISTAS ENLAZADAS

Voy a copiar la teoría del curso de Cabanes ya que esta mucho mejor explicado de lo que lo haría yo

y continuare con el ejemplo en el IDA.

Estructuras dinámicas habituales 1: las listas enlazadas

Ahora vamos a ver un tipo de estructura totalmente dinámica (que puede aumentar o disminuir

realmente de tamaño durante la ejecución del programa). Estas son las llamadas listas.

Ahora “el truco” consistirá en que dentro de cada dato almacenaremos todo lo que nos interesa,

pero también una referencia que nos dirá dónde tenemos que ir a buscar el siguiente.

Sería algo como:

(Posición: 1023).

Nombre : 'Nacho Cabanes'

Web : 'www.nachocabanes.com'

SiguienteDato : 1430

Este dato está almacenado en la posición de memoria número 1023. En esa posición guardamos el

nombre y la dirección (o lo que nos interese) de esta persona, pero también una información extra:

la siguiente ficha se encuentra en la posición 1430.

Así, es muy cómodo recorrer la lista de forma secuencial, porque en todo momento sabemos dónde

está almacenado el siguiente dato. Cuando lleguemos a uno para el que no esté definido cual es el

siguiente dato, quiere decir que se ha acabado la lista.

Por tanto, en cada dato tenemos un enlace con el dato siguiente. Por eso este tipo de estructuras

recibe el nombre de “listas simplemente enlazadas” o listas simples. Si tuviéramos enlaces hacia el

dato siguiente y el posterior, se trataría de una “lista doblemente enlazada” o lista doble, que

pretende hacer más sencillo el recorrido hacia delante o hacia atrás.

Con este tipo de estructuras de información, hemos perdido la ventaja del acceso directo: ya no

podemos saltar directamente a la ficha número 500. Pero, por contra, podemos tener tantas fichas

como la memoria nos permita, y eliminar una (o varias) de ellas cuando queramos, recuperando

inmediatamente el espacio que ocupaba.

Para añadir una ficha, no tendríamos más que reservar la memoria para ella, y el compilador de C

nos diría “le he encontrado sitio en la posición 4079”. Entonces nosotros iríamos a la última ficha y

le diríamos “tu siguiente dato va a estar en la posición 4079”.

Esa es la “idea intuitiva”. Ahora vamos a concretar cosas en forma de programa en C.

Primero veamos cómo sería ahora cada una de nuestras fichas:

struct f { /* Estos son los datos que guardamos: */

char nombre[30]; /* Nombre, hasta 30 letras */

char direccion[50]; /* Direccion, hasta 50 */

int edad; /* Edad, un numero < 255 */

struct f* siguiente; /* Y dirección de la siguiente */

};

Page 226: c y Reversing

La diferencia con un “struct” normal está en el campo “siguiente” de nuestro registro, que es el

que indica donde se encuentra la ficha que va después de la actual, y por tanto será otro puntero a

un registro del mismo tipo, un “struct f *”.

Un puntero que “no apunta a ningún sitio” tiene el valor NULL (realmente este identificador es una

constante de valor 0), que nos servirá después para comprobar si se trata del final de la lista: todas

las fichas “apuntarán” a la siguiente, menos la última, que “no tiene siguiente”, y apuntará a NULL.

Entonces la primera ficha definiríamos con

struct f *dato1; /* Va a ser un puntero a ficha */

y la comenzaríamos a usar con

dato1 = (struct f*) malloc (sizeof(struct f)); /* Reservamos memoria */

strcpy(dato1->nombre, "Pepe"); /* Guardamos el nombre, */

strcpy(dato1->direccion, "Su casa"); /* la dirección */

dato1->edad = 40; /* la edad */

dato1->siguiente = NULL; /* y no hay ninguna más */

(No debería haber nada nuevo: ya sabemos cómo reservar memoria usando “malloc” y como

acceder a los campos de una estructura dinámica usando ->).

Ahora que ya tenemos una ficha, podríamos añadir otra ficha detrás de ella. Primero guardamos

espacio para la nueva ficha, como antes:

struct f *dato2;

dato2 = (struct f*) malloc (sizeof(struct f)); /* Reservamos memoria */

strcpy(dato2->nombre, "Juan"); /* Guardamos el nombre, */

strcpy(dato2->direccion, "No lo sé"); /* la dirección */

dato2->edad = 35; /* la edad */

dato2->siguiente = NULL; /* y no hay ninguna más */

y ahora enlazamos la anterior con ella:

dato1->siguiente = dato2;

Si quisiéramos introducir los datos ordenados alfabéticamente, basta con ir comparando cada nuevo

dato con los de la lista, e insertarlo donde corresponda. Por ejemplo, para insertar un nuevo dato

entre los dos anteriores, haríamos:

struct f *dato3;

dato3 = (struct f*) malloc (sizeof(struct f)); /* La tercera */

strcpy(dato3->nombre, "Carlos");

strcpy(dato3->direccion, "Por ahí");

dato3->edad = 14;

dato3->siguiente = dato2; /* enlazamos con la siguiente */

dato1->siguiente = dato3; /* y la anterior con ella */

printf("La lista inicialmente es:\n");

Page 227: c y Reversing

La estructura que hemos obtenido es la siguiente

Dato1 - Dato3 - Dato2 - NULL

Gráficamente:

Es decir: cada ficha está enlazada con la siguiente, salvo la última, que no está enlazada con

ninguna (apunta a NULL).

Si ahora quisiéramos borrar Dato3, tendríamos que seguir dos pasos:

1.- Enlazar Dato1 con Dato2, para no perder información.

2.- Liberar la memoria ocupada por Dato3.

Esto, escrito en "C" sería:

dato1->siguiente = dato2; /* Borrar dato3: Enlaza Dato1 y Dato2 */

free(dato3); /* Libera lo que ocupó Dato3 */

Hemos empleado tres variables para guardar tres datos. Si tenemos 20 datos, ¿necesitaremos 20

variables? ¿Y 3000 variables para 3000 datos?

Sería tremendamente ineficiente, y no tendría mucho sentido. Es de suponer que no sea así. En la

práctica, basta con dos variables, que nos indicarán el principio de la lista y la posición actual, o

incluso sólo una para el principio de la lista.

Por ejemplo, una rutina que muestre en pantalla toda la lista se podría hacer de forma recursiva así:

void MuestraLista ( struct f *inicial ) {

if (inicial!=NULL) { /* Si realmente hay lista */

printf("Nombre: %s\n", inicial->nombre);

printf("Dirección: %s\n", inicial->direccion);

printf("Edad: %d\n\n", inicial->edad);

MuestraLista ( inicial->siguiente ); /* Y mira el siguiente */

}

}

Lo llamaríamos con "MuestraLista(dato1)", y a partir de ahí el propio procedimiento se encarga de

ir mirando y mostrando los siguientes elementos hasta llegar a NULL, que indica el final.

Antes de seguir, vamos a juntar todo esto en un programa, para comprobar que realmente funciona:

añadimos los 3 datos y decimos que los muestre desde el primero; luego borramos el del medio y

los volvemos a mostrar:

Hasta aquí la explicación teórica que creo se entiende perfectamente, así que vayamos al código

fuente a explicarlo y luego a verlo en IDA.

Page 228: c y Reversing

Veamoslo en IDA ahora veremos que no es tan complicado como parece.

Page 229: c y Reversing

Vemos que en el main no tenemos variables locales, ya que las que estan allí son las que siempre

crea y usa el procesador y no son nuestras, si recordamos el código fuente veamos que no hay

variables locales definidas dentro de main, las que usa son todas globales.

Aquí comienza lo nuestro, reserva 58 hexa de tamaño que es 88 decimal para la primera ficha o

ficha1, recordamos que era 30 para el primer campo, 50 para el segundo, 4 para la edad y 4 para el

puntero al siguiente lo que da 88 decimal.

Page 230: c y Reversing

El puntero que en nuestro código llamábamos dato1 lo guarda en la variable global 404080, así que

renombramos allí a dato1.

Luego llama a strcpy pasandole el puntero a la string “Pepe” como fuente y como destination el

puntero dato1, para que inicialice copiando allí el primer campo de la ficha.

Luego corre el puntero desde el inicio 1Eh o sea 30 decimal mas adelante para escribir la dirección

que era el segundo campo.

Podemos ponerlo en decimal usando el menú del botón derecho allí.

Luego guarda la edad y el cero en el campo siguiente ya que es la ultima ficha creada en esta lista,

sumándole 50 al puntero dato1 y escribiendo en el contenido que sera el campo edad y luego

sumándole 54 al puntero dato1 y escribiendo en el contenido que sera el campo siguiente.

Podemos pasarlo a decimal así se ve correctamente la edad y apretando punto y coma puedo agregar

algún comentario adicional como que el 40 es la edad y que el cero es el que marca la condición de

ultimo.

Page 231: c y Reversing

Vemos que hace exactamente lo mismo con la segunda ficha o ficha2 en la lista, reserva la

memoria le copia los datos y pone el cero en el campo siguiente, lo que queda ahora es ver como

arregla el campo siguiente de la ficha1 para quitar el cero y poner el puntero a la ficha2.

Ahí esta mueve a EDX dato1 el puntero a la ficha1 y a EAX dato2 el puntero a ficha2 y luego

escribe en el contenido de dato1 mas 54h o sea en el campo siguiente de la ficha1, el puntero dato2

para que apunte a la ficha2 y se mantenga la lista enlazada.

Luego realiza el mismo trabajo con la ficha3.

Y luego arregla los punteros haciendo que esta ficha3 se incluya en medio de las dos existentes,

quede como la segunda en la lista enlazada, vemos primero que al siguiente de la ficha3 le mueve el

puntero a la ficha2, y luego al siguiente de la ficha1 hace que apunte a ficha3 para que queden en la

lista en el orden

ficha1--> ficha3 → ficha2

Page 232: c y Reversing

mov dword ptr [esp], offset aLaListaInicial ; "La lista inicialmente es:\n"

call printf

Luego de imprimir el mensaje anterior llama a la funcion que en nuestro código se llamaba

MuestraLista, así que la renombramos.

Entrando en ella vemos que tiene un argumento ya que se le pasaba el puntero a la ficha1 llamado

dato1.

Sabemos que dentro de la funcion tomara los valores de dato1, dato2 o dato3 lo llamaremos datoX

para hacerlo bien genérico.

Luego propagamos la definición de la funcion con el argumento datoX, yendo a Set Function Type.

Page 233: c y Reversing

Así propago el nombre del argumento hacia main, veo que en el main aparece, como que le paso

dato1 como argumento y que la funcion lo recibe como datoX ya que es genérico para los

diferentes llamados que hay a la misma.

Aquí se fija si datoX es cero eso solo puede ocurrir si no hay lista o si la ficha es la ultima, en este

caso datoX vale dato1 y es distinto de cero.

Así que usando ese puntero a dato1 llama a printf haciendo format string %s imprimiendo el

campo nombre, luego le suma 30 para hallar el puntero al campo dirección y mediante format

string imprimir la misma.

Page 234: c y Reversing

Luego le suma 50h al puntero dato1 y lee la edad y hace format string usando %d para imprimir la

misma.

Luego llama a la misma funcion recursivamente, pasandole como argumento el campo siguiente

que esta 54h a partir de dato1, y vuelve a repetir el mismo proceso para imprimir los datos de la

siguiente ficha de la lista y así sucesivamente hasta que llegue a la ultima que tiene valor cero en el

campo siguiente y por eso sale y vuelve al main.

Luego elimina la ficha3, para ello arregla los punteros y al siguiente de la ficha1, le guarda el

puntero dato2, y hace free() de dato3 con lo cual la elimina.

Luego imprime nuevamente toda la lista pasandole el puntero a ficha1, ahora solo imprimirá la

ficha1, y la ficha2, ya que la ficha3 desapareció por el free() y el arreglo de los punteros siguiente.

Page 235: c y Reversing

Eso es todo sobre el ejemplo de lista simplemente enlazada veremos si alguno es guapo y reversea

el ejercicio que es una lista simplemente enlazada mas compleja que este jeje.

Hasta la parte siguiente:

ricnar

Page 236: c y Reversing

Operaciones con bits

Podemos hacer desde C operaciones entre bits de dos números (AND, OR, XOR, etc). Vamos

primero a ver qué significa cada una de esas operaciones.

Veamoslo todos en un mismo ejemplo.

#include <stdio.h>

int main() {

int a = 67;

int b = 33;

printf("La variable a vale %d\n", a);

printf("y b vale %d\n\n", b);

printf(" El complemento de a es: %d\n", ~a);

printf(" El producto logico de a y b es: %d\n", a&b);

printf(" Su suma logica es: %d\n", a|b);

printf(" Su suma logica exclusiva es: %d\n", a^b);

printf(" Desplacemos a a la izquierda: %d\n", a << 1);

printf(" Desplacemos a a la derecha: %d\n", a >> 1);

getchar();

return 0;

}

Al correrlo vemos los resultados de las operaciones.

Page 237: c y Reversing

Llevado a binario podemos comprobar fácilmente las operaciones:

A=1000011 = 43h

B=100001 = 21h

El complemento de A o sea NOT (A) es FFFFFFFFh – 43h = FFFFFFBCh

Tengo justo un Olly abierto que es rápido para hacer conversiones y veo que FFFFFFBCh es -68h si

lo tomamos con signo.

Luego hace el producto lógico o AND entre ambos, los encolumno en forma binaria.

Sabemos que mirando columna por columna solo tendrá la misma resultado uno si ambos bits de la

misma son 1 sino cualquier otra combinación da cero.

A=1000011

B=0100001

--------------

C=0000001

Allí C sera el resultado y valdrá uno ya que solo la ultima columna de la operación tiene ambos bits

encendidos en uno, de esta forma el producto lógico o AND entre ambos da 1h.

Luego viene la suma lógica o OR encolumnamos en binario nuevamente sabiendo en este caso que

la suma lógica mientras que haya un uno en algún bit de una columna ya el resultado sera 1.

A=1000011

B=0100001

--------------

C=1100011

Que es igual a 63h o sea 99

Luego hacer la suma exclusiva o XOR que sabemos que solo da 1 si ambos bits de la columna son

distintos.

Page 238: c y Reversing

A=1000011

B=0100001

--------------

C=1100010

Que es igual a 62h o sea 98

Luego viene el desplazamiento a la izquierda de A , corremos todo para la izquierda un lugar y le

agregamos un cero por la derecha para rellenar el espacio que quedo vacío al correr.

A=1000011

C=10000110

O sea el resultado es 86h o sea 134

Lo mismo el desplazamiento hacia la derecha

A=1000011 -- al desplazar a la derecha ese byte se cae fuera del limite

C=0100001 - y es rellenado con un cero por el otro lado.

Que es 33 decimal

Es de notar que en el desplazamiento hay que tener en cuenta el limite máximo a cada lado pues al

desplazar a la izquierda como solo desplazamos uno no pasamos el limite máximo de bits, si lo

hiciéramos, los que se cayeran fuera se perderían siendo reemplazados por los ceros que se rellenan

por el otro lado, en el caso del desplazamiento a la derecha es igual solo que allí estamos en el

limite, así que al desplazar solo un lugar el ultimo byte se cae fuera siendo rellenado con un cero

por delante.

Bueno todo muy lindo veamos como se ve todo esto en IDA.

Page 239: c y Reversing

Allí vemos donde empieza realmente lo nuestro y las dos variables que son int o sea ocupan un

dword cada una, comencemos a renombrar.

Lo pusimos en decimal como en el código fuente y le cambiamos los nombres obvio que si no

tenemos el fuente y estamos reverseando le pondremos los nombres que se nos antoje.

Page 240: c y Reversing

Lo primero que hace es imprimir los valores de a y b en dos printf usando %d en el format string.

Luego imprime el valor del complemento de a, allí vemos como hace NOT que es la instrucción

para hallarlo.

Luego hace el producto lógico o AND para ellos mueve b a EAX y luego hace AND de EAX con a,

el resultado lo manda a imprimir con printf.

De la misma forma halla la suma lógica OR y la suma lógica exclusiva XOR y los manda a

imprimir.

Luego realiza el desplazamiento a la izquierda que en el caso de desplazar uno es equivalente a

multiplicar por 2, recordemos que a valía 67 y el resultado era 134.

Page 241: c y Reversing

Si en el código fuente cambiáramos que se desplace dos posiciones vemos que ahora si necesita

usar el SHL o SAL que es la instrucción de desplazamiento a la izquierda.

http://ensam1.blogspot.com/2005/09/64-corrimiento-y-rotacion.html

allí mas de SHL SAL SHR y SAR

y allí usa SAR para el ultimo desplazamiento a la derecha y con eso termina el ejemplo que es bien

sencillo.

Una lección sencilla y un ejercicio sencillo para reversear sobre el tema jeje.

Hasta la parte siguiente

ricnar

Page 242: c y Reversing

INCLUDE Y DEFINE

Aquí copiare directo de la teoría de Cabanes que esta muy bien explicada, esto que viene sobretodo

las macros son mas importantes para el que programa que para un reverser, ya que estas macros son

resueltas antes de compilar y no se podrá ver nada diferente en el IDA, si se define una macro se

vera en el IDA el resultado de aplicar la misma directamente y sera difícil adivinar de donde

provino pues no veremos código ni nada, pero es bueno conocer del tema aunque sea por encima así

que aquí va la teoría,

Directivas del preprocesador

Desde el principio hemos estado manejando cosas como

#include <stdio.h>

Y aquí hay que comentar bastante más de lo que parece. Ese “include” no es una orden del lenguaje

C, sino una orden directa al compilador (una “directiva”). Realmente es una orden a una cierta parte

del compilador que se llama “preprocesador”. Estas directivas indican una serie de pasos que se

deben dar antes de empezar realmente a traducir nuestro programa fuente.

Aunque “include” es la directiva que ya conocemos, vamos a comenzar por otra más sencilla, y que

nos resultará útil cuando lleguemos a ésta.

Constantes simbólicas: #define

La directiva “define” permite crear “constantes simbólicas”. Podemos crear una constante haciendo

#define MAXINTENTOS 10

y en nuestro programa lo usaríamos como si se tratara de cualquier variable o de cualquier valor

numérico:

if (intentoActual >= MAXINTENTOS) ...

El primer paso que hace nuestro compilador es reemplazar esa “falsa constante” por su valor, de

modo que la orden que realmente va a analizar es

if (intentoActual >= 10) ...

pero a cambio nosotros tenemos el valor numérico sólo al principio del programa, por lo que es

muy fácil de modificar, mucho más que si tuviéramos que revisar el programa entero buscando

dónde aparece ese 10.

Comparado con las constantes “de verdad”, que ya habíamos manejado (const int

MAXINTENTOS=10;), las constantes simbólicas tienen la ventaja de que no son variables, por lo

que no se les reserva memoria adicional y las comparaciones y demás operaciones suelen ser más

rápidas que en el caso de un variable.

Vamos a ver un ejemplo completo, que pida varios números y muestre su suma y su media:

Page 243: c y Reversing

#include <stdio.h>

#define CANTIDADNUMEROS 5

int main() {

int numero[CANTIDADNUMEROS];

int suma=0;

int i;

for (i=0; i<CANTIDADNUMEROS; i++) {

printf("Introduzca el dato número %d: ", i+1);

scanf("%d", &numero[i]);

}

for (i=0; i<CANTIDADNUMEROS; i++)

suma += numero[i];

printf("Su suma es %d\n", suma);

printf("Su media es %4.2f\n", (float) suma/CANTIDADNUMEROS);

getchar();

getchar();

return 0;

}

Vemos que es un programita muy sencillo crea un array de enteros de tamaño 5, ya que el define del

inicio hace que CANTIDADNUMEROS sea igual a 5 en todos los casos

int numero[CANTIDADNUMEROS];

sera igual a

int numero[5];

y así el preprocesador reemplazara donde encuentre la palabra CANTIDADENUMEROS por el

valor 5 y lo compilara.

De esta forma el código anterior seria equivalente a

#include <stdio.h>

int main() {

int numero[5];

int suma=0;

int i;

for (i=0; i<5; i++) {

printf("Introduzca el dato número %d: ", i+1);

scanf("%d", &numero[i]);

}

Page 244: c y Reversing

for (i=0; i<5; i++)

suma += numero[i];

printf("Su suma es %d\n", suma);

printf("Su media es %4.2f\n", (float) suma/5);

getchar();

getchar();

return 0;

}

Si compilamos de nuevo el original con el define y lo vemos en IDA.

Page 245: c y Reversing

Allí vemos las variables y argumentos sabemos que hay variables y argumentos que agrego el

compilador, pero si no estamos seguros cuales son, pues empecemos por la superior una por una a

ver que es cada una.

Apretando la X en la primera vemos de donde es llamada y eso es aquí:

Así que esa solo trabaja en la parte anterior a que empiece el código real, así que es agregada por el

compilador.

La siguiente ni siquiera tiene referencias así que lo mismo es creada por el compilador

La tercera ya es usada en el código real veamos que es.

Page 246: c y Reversing

Vemos que es inicializada con cero aquí y luego comparada con 4.

Se ve claramente que es el contador de un loop, mas adelante hay otra iniclializacion con cero, y

compara con cuatro, así que es posible que se reuse varias veces como contador.

Page 247: c y Reversing

Dentro del loop la vemos usada tres veces, la primera para pasarle al printf el numero de dato que

debemos ingresar ya que hace format string usando el valor del contador mas uno, así cuando en el

loop el contador valga cero, sin cambiar el valor de la variable se moverá a EAX y allí on the fly se

incrementa a uno y luego se imprime “Introduzca el dato numero 1”.

Se entiende que el “on the fly” lo uso porque contador no cambia su valor por el INC sino que lo

hace al vuelo incrementando en un registro.

lea edx, [ebp+var_28]

mov eax, [ebp+var_30]

shl eax, 2

Aquí lo usa para recorrer un array de enteros, aunque no tenga el código fuente me doy cuenta que

levanta la dirección del inicio de un array con el LEA y mueve a EAX el valor de contador y lo

multiplica por 4 (SHL EAX, 2)

Así que cuando el contador valga 0 luego del SHL, tendremos que EAX valdrá 0, en el próximo

ciclo cuando contador valga 1, EAX valdrá 4 y así al sumarlo a la dirección de inicio del array

podremos usarlo como indice para recorrer los campos del mismo.

lea eax, [edx+eax]

mov [esp+4], eax

mov dword ptr [esp], offset aD ; "%d"

call scanf

Eso es lo que hace suma usando LEA la dirección de inicio del array que estaba en EDX con EAX

obteniendo la dirección del campo actual del array y eso lo pasa como argumento a scanf para que

ingresemos los datos y vayamos llenando los campos del array en cada ciclo del loop, así que

podemos ir arreglando todo esto, podemos renombrar la var_30 a contador.

Page 248: c y Reversing

Y la var_28 vimos que era un array ya que ahí en el loop se inicializaban los campos, sabemos que

nos pide 5 datos enteros y que el for sale cuando es mas grande que 4.

Por lo tanto podemos suponer que el array tiene 5 campos enteros de cualquier forma si uno usa

todo el espacio vacío y no hay ninguna otra referencia en el programa que nos diga lo contrario, no

esta mal, así que vamos a crear el array.

Page 249: c y Reversing

Verificamos que el tamaño de cada campo sera correcto en este caso 4 ya que es un array de enteros,

cambiamos el array size a 5 y apretamos OK.

Quedo un poco de espacio vacío que dejo el compilador pero no hay problema renombro la variable

Page 250: c y Reversing

array a datos.

Una vez que sale del loop que inicializa los datos en el array el cual pinte de rosado, vemos que se

vuelve a poner a cero la variable contador y parece haber otro loop-

Y si hay un segundo loop allí pintado de amarillo

Mientras que contador sea menor o igual a cuatro se repetirá el loop yendo por los bloques

amarillos, veamos que hace dentro del mismo.

Page 251: c y Reversing

Lee el contador del loop

mov eax, [ebp+contador]

Mueve a EDX los valores ingresados en cada campo

mov edx, [ebp+eax*4+datos]

Creo que es fácil de ver que cuando contador vale

contador=0

mov edx, [ebp+0*4+datos]

mov edx, [ebp+datos]

Quedara en EDX el valor guardado en el primer campo del array

contador=1

mov edx, [ebp+1*4+datos]

mov edx, [ebp+4+datos]

Quedara en EDX el valor guardado en el segundo campo del array y así sucesivamente.

Luego usa una var_2c para ir sumando todos los valores guardados en los campos.

lea eax, [ebp+var_2C]

add [eax], edx

Así que vemos que la var_2c que nos quedaba renombrar podemos llamarla suma ya que allí

guarda la suma de los valores que ingresamos.

Luego incrementa el contador y va al inicio del loop donde se fijara si el mismo es mayor que

cuatro para salir del mismo.

Page 252: c y Reversing

Luego de salir del loop imprime usando printf el valor de suma usando format string.

Luego usando el stack de punto flotante carga el valor de la suma en el mismo, y luego un 5 que lo

guardo como variable global en 403044, luego hará la división entre ambos lo cual es la media, la

cual imprimirá.

El chiste de todo esto es que nunca vemos CANTIDADNUMEROS sino que para nosotros al

reversear sera 5 la variable global y sera cinco la cantidad de veces que loopeara y ni nos enteramos

de lo que hizo el preprocesador con el define, reverseando llegaríamos a esto:

#include <stdio.h>

int main() {

int datos[5];

int suma=0;

int contador;

for (contador=0; contador<5; contador++) {

printf("Introduzca el dato número %d: ", contador+1);

scanf("%d", &datos[contador]);

}

for (contador=0; contador<5; contador++)

suma += datos[contador];

printf("Su suma es %d\n", suma);

printf("Su media es %4.2f\n", (float) suma/5);

getchar();

getchar();

return 0;

}

Page 253: c y Reversing

A “define” también se le puede dar también un uso más avanzado: se puede crear “macros”, que en

vez de limitarse a lo antes visto, pueden comportarse como pequeñas órdenes, más rápidas que una

función. Un ejemplo podría ser:

#define SUMA(x,y) x+y

aquí el código

#include <stdio.h>

#define SUMA(x,y) x+y

int main() {

int n1, n2;

printf("Introduzca el primer dato: ");

scanf("%d", &n1);

printf("Introduzca el segundo dato: ");

scanf("%d", &n2);

printf("Su suma es %d\n", SUMA(n1,n2));

getchar();

getchar();

return 0;

}

Lo que hará el preprocesador es un replace antes de compilar, o sea donde halle el texto SUMA(x,y)

lo reemplazara por el texto x+y y para el reverser el código sera.

#include <stdio.h>

int main() {

int n1, n2;

printf("Introduzca el primer dato: ");

scanf("%d", &n1);

printf("Introduzca el segundo dato: ");

Page 254: c y Reversing

scanf("%d", &n2);

printf("Su suma es %d\n", (n1+n2));

getchar();

getchar();

return 0;

}

Si lo vemos en IDA vemos que no existe ninguna funcion SUMA ni nada por el estilo, el

preprocesador reemplazo texto y quedo una suma directa entre dos variables int.

Page 255: c y Reversing

Así que el tema macros a pesar de ser útil para programar es como magia anterior a la compilación,

puro replace de texto que realmente el reverser nunca vera, solo hallara, si lo hace correctamente el

código que el preprocesador ya manipulo y esta listo para la compilación final que sera igualmente

funcional que el original aunque menos elegante posiblemente.

Volvamos a la teoría de Cabanes:

Inclusión de ficheros: #include

Ya nos habíamos encontrado con esta directiva. Lo que hace es que cuando llega el momento de que

nuestro compilador compruebe la sintaxis de nuestro fuente en C, ya no existe ese “include”, sino

que en su lugar el compilador ya ha insertado los ficheros que le hemos indicado.

¿Y eso de por qué se escribe <stdio.h>, entre < y >? No es la única forma de usar #include.

Podemos encontrar líneas como

#include <stdlib.h>

y como

#include "misdatos.h"

El primer caso es un fichero de cabecera estándar del compilador. Lo indicamos entre < y > y así el

compilador sabe que tiene que buscarlo en su directorio (carpeta) de “includes”. El segundo caso es

un fichero de cabecera que hemos creado nosotros, por lo que lo indicamos entre comillas, y así el

compilador sabe que no debe buscarlo entre sus directorios, sino en el mismo directorio en el que

está nuestro programa.

Vamos a ver un ejemplo: declararemos una función “suma” dentro de un fichero “.h” y lo

incluiremos en nuestro fuente para poder utilizar esa función “suma” sin volver a definirla. El

fichero de cabecera se llamaría c096.h:

Lo creamos como archivo de texto lo renombramos a c096.h, le pegamos este contenido,

colocandolo en la misma carpeta donde estará el archivo fuente que lo llamara.

int suma(int x,int y) {

return x+y;

}

Page 256: c y Reversing

(Nota: si somos puristas, esto no es correcto del todo. Un fichero de cabecera no debería contener

los detalles de las funciones, sólo su “cabecera”, lo que habíamos llamado el “prototipo”, y la

implementación de la función debería estar en otro fichero, pero eso lo haremos dentro de poco).

Un fuente que utilizara este fichero de cabecera, lo creamos y lo guardamos en la misma carpeta del

anterior.

#include <stdio.h>

#include "c096.h"

int main() {

int n1, n2;

printf("Introduzca el primer dato: ");

scanf("%d", &n1);

printf("Introduzca el segundo dato: ");

scanf("%d", &n2);

printf("Su suma es %d\n", suma(n1,n2));

getchar();

getchar();

return 0;

}

Vemos que encuentra perfectamente al archivo c096.h y usa la funcion suma que esta en el mismo.

Page 257: c y Reversing

Vemos en el IDA que el reverser no ve que la funcion usada no esta en el .c del fuente.

Solo aparece una funcion mas que es la que hará la suma pero podría ser una funcion incluida en el

.c perfectamente y no podríamos diferenciarlo aquí.

Page 258: c y Reversing

Allí la funcion que realiza la suma.

Bueno esta semana no habrá ejercicios porque el tema es poco dado para el reversing aunque había

que conocerlo, así que los veo la semana que viene con la siguiente parte.

Ricnar

Page 259: c y Reversing

Uniones y campos de bits

Nos quedan ver algunos detalles del lenguaje C que aunque no tan usados es buenos conocer y ver

como los veremos en IDA después de compilados, para el reversing.

Le cedo la palabra a Cabanes sobre el tema Uniones, copio la explicación.

Conocemos lo que es un struct: un dato formado por varios “trozos” de información de distinto tipo.

Pero C también tiene dos tipos especiales de “struct”, de manejo más avanzado. Son las uniones y

los campos de bits.

Una unión recuerda a un “struct” normal, con la diferencia de que sus “campos” comparten el

mismo espacio de memoria:

union {

char letra; /* 1 byte */

int numero; /* 4 bytes */

} ejemplo;

En este caso, la variable “ejemplo” ocupa 4 bytes en memoria (suponiendo que estemos trabajando

en un compilador de 32 bits, como lo son la mayoría de los de Windows y Linux). El primer byte

está compartido por “letra” y por “numero”, y los tres últimos bytes sólo pertenecen a “numero”.

Si hacemos

ejemplo.numero = 25;

ejemplo.letra = 50;

printf("%d", ejemplo.numero);

Veremos que “ejemplo.numero” ya no vale 25, puesto que al modificar “ejemplo.letra” estamos

cambiando su primer byte. Ahora “ejemplo.numero” valdría 50 o un número mucho más grande,

según si el ordenador que estamos utilizando almacena en primer lugar el byte más significativo o el

menos significativo.

#include <stdio.h>

int main() {

union {

char letra; /* 1 byte */

int numero; /* 4 bytes */

} ejemplo;

int n1, n2;

ejemplo.numero = 25;

ejemplo.letra = 50;

printf("%d", ejemplo.numero);

getchar();

Page 260: c y Reversing

getchar();

return 0;

}

Vemos que el campo ejemplo.numero comparte memoria con ejemplo.letra y al cambiar el valor

de este ultimo afectamos el valor del primero veamoslo en IDA a ver como se ve y si hay

posibilidad de reversear esto.

Vemos que IDA no ha hecho demasiado por nuestra union la ha puesto como una sola variable int

llamada var_4 y listo, un reverser le costara en este ejemplo darse cuanta que hay una union allí,

pero hay algunas pistas como por ejemplo que cuando inicializa vemos que escribe un dword 19,

siendo que es lógico ya que es un int, pero en la siguiente linea le escribe un byte solo a la misma

Page 261: c y Reversing

variable var_4

mov [ebp+var_4], 19h

mov byte ptr [ebp+var_4], 32h

No tiene mucho sentido si es solo una variable int inicializar su valor con un dword 19h y luego

escribir un solo byte en la misma en este caso el 32h, ahí sospechamos de que hay una union entre

dos variables una int y una char.

En IDA para definir la unión vamos a la pestaña estructuras y agregamos una como siempre.

Solo que esta vez marcamos la tilde Create union y agregamos dos campos uno dword y uno de un

solo byte, el tamaño de la unión siempre sera el del campo mas grande en este caso 4 pues hay un

dword.

Los renombramos como deseamos

Yendo a la ventana de variables apretando ALT mas Q encima de var_4, elegimos que sea del tipo

de la unión que hemos creado.

Page 262: c y Reversing

Cambiamos el nombre a la variable también.

Igual a pesar de todo el trabajo IDA no logra marcar los campos de las uniones como diferentes y

sigue tomando como una sola variable aunque del tipo unión.

Aunque si pasamos el mouse por dicha variable veremos que nos muestra como son los campos de

dicha unión.

Page 263: c y Reversing

Enumeraciones

Una enumeración es un conjunto de constantes enteras. A la enumeración se le puede asignar un

nombre, que se comportará como un nuevo tipo de dato que solo podrá contener los valores

especificados en la enumeración.

enum dia { DOM, LUN, MART, MIER, JUEV, VIER, SAB } diaX;

int main() {

diaX=LUN;

printf ("%d", diaX);

getchar();

}

Allí hay un típico caso de enumeración donde se relacionan por orden los días de la semana con un

numero, si no especificamos nada, sera DOM=0, LUN=1 etc, si corremos este programa vemos que

su salida es 1, ya que LUN esta relacionado con el numero 1, para el tipo de variable dia.

Page 264: c y Reversing

Si queremos agregar mas variables del tipo día podemos hacerlo, todas respetaran la relación que

definimos al inicio entre los días y un numero entero.

También se podría haber hecho.

Page 265: c y Reversing

Si lo vemos en IDA no veremos mucho mas que estoç

La variable global diaX ya que fue definida antes del main, esta en 404060, podemos renombrarla.

Page 266: c y Reversing

En cambio la var_8 que es también del tipo día pero definida localmente, la renombraremos a

festivo.

Ahora vamos a la pestaña enumeraciones y agregamos una apretando INS tal cual hacemos con las

estructuras.

Page 267: c y Reversing

Ponemos decimal ya que queremos que se asocie con enteros decimales para que sea mas sencillo.

Apretando N vamos agregando los campos DOM sera 0 y así sucesivamente, salvo MIER que sera

9.

Ahí esta lista vamos a las variables y elegimos el tipo de enumeración que creamos

Page 268: c y Reversing

Si vamos al código parece que nada cambio y es así, pero si hacemos click derecho en el 9.

Page 269: c y Reversing

Nos da la opción de poner MIER como definimos en la enum para el 9.

Lo mismo para la variable global diaX la marcamos y

Page 270: c y Reversing

Y nos quedan cambiadas las constantes

CAMPOS DE BITS

Un campo de bits es un elemento de un registro (struct), que se define basándose en su tamaño en

bits. Se define de forma muy parecida (pero no igual) a un "struct" normal, indicando el número de

bits que se debe reservar a cada elemento:

struct campo_de_bits {

int bit_1 : 1;

int bits_2_a_5 : 4;

int bit_6 : 1;

int bits_7_a_16 : 10;

} variableDeBits;

Esta variable ocuparía 1+4+1+10 = 16 bits (2 bytes). Los campos de bits pueden ser interesantes

cuando queramos optimizar al máximo el espacio ocupado por nuestro datos.

#include <stdio.h>

int main() {

struct campo_de_bits {

int bit_1 : 1;

int bits_2_a_5 : 4;

int bit_6 : 1;

int bits_7_a_16 : 10;

} variableDeBits;

Page 271: c y Reversing

variableDeBits.bit_1 = 0;

variableDeBits.bits_2_a_5 = 3;

variableDeBits.bit_6 =0;

printf("%d", variableDeBits);

getchar();

getchar();

return 0;

}

Bueno vemos un campo de bits, y como asignamos valores, vemos que al asignar diferentes valores

a cada campo, el valor numérico de la variableDeBits cambia, obviamente pues estamos

cambiando sus bits.

En el ejemplo valdrá 6 pues estamos asignando el decimal 3 que es 11 a la posición 2 a 5 o sea que

0011 va al bit 2 a 5

o sea que variableDeBits quedara en binario 000110 que es el 6 decimal.

Si lo vemos en IDA no ganamos mucho para reversear también es trabajado todo sobre una solo

variable y realizadas las operaciones usando ANDs y ORs para cambiar los resultados de los bits.

Page 272: c y Reversing

Existe en la pestaña ENUM la opción de crear campos de bits, pero realmente no aporta mucho al

reversing así que lo vamos solo a mencionar sin entrar en detalles, pues IDA aunque lo definas no

cambia el listado así que para el reverser no aporta mucho solo es bueno para conocer el tema.

Bueno hemos terminado con lo básico para empezar a reversear a partir de la semana que viene

comenzaremos reverseando pequeños ejemplos que iremos programando nosotros mismos,

asimismo habrá ejercicios de reversing ahora que terminamos la cucharada mas difícil que es ver la

base teórica, el resto es remar con dos tenedores como remos jeje.

Hasta la próxima

Ricnar

Page 273: c y Reversing

REVERSEANDO Y PRACTICANDO

Antes de seguir con las practicas quería mostrar un par de detalles que me falto explicar y que serán

necesarios en estos ejemplos de reversing.

Lo primero es como se ingresan en C argumentos por consola.

#include <windows.h>

#include <stdio.h>

int main(int argc, char* argv[])

{

int i;

if (argc >1){

printf("Cantidad de argumentos es %d\n", argc);

for (i=0; i<argc; i++) printf("argumentos son %s\n", argv[i]);

}

else {

printf("Tipear argumentos para probar\n");

}

getchar();

}

C provee un mecanismo para pasar argumentos desde la línea de comandos al programa que se va a

ejecutar. Cuando el programa comienza su ejecución, la rutina main es llamada con dos argumentos:

un contador y un puntero a un array de strings. El contador es llamado por convención argc y el

apuntador argv. El uso de de argv es un poco truculento. Dado que argv es un puntero a un array de

strings, la primera cadena de caracteres es referenciada por argv[0] (o *argv). La segunda cadena is

referenciada por argv[1] (o *(argv + 1)), la tercera por argv[2], y así sucesivamente.

La primera cadena de caracteres, argv[0], contiene el nombre del programa. Los argumentos

comienzan realmente con argv[1].

Vemos que las argc y argv no necesitan ser declaradas como dijimos siempre el compilador las

maneja, si vemos este código en el IDA.

Page 274: c y Reversing

Vemos que el main ya tiene como argumentos argc y argv que son dos dwords ya que el primero es

un contador o sea un int y el segundo es un puntero a un array de punteros a strings, por lo tanto

también es un int.

Vemos que compara la cantidad de argumentos con uno, ya que siempre el primer argumento es el

mismo nombre del ejecutable o sea que siempre sera 1 como mínimo.

Vemos que si es menor o igual que uno tomara el camino de la flecha verde y me pedirá que tipee

argumentos para probar, mientras que si tipee alguno, ira por el camino rojo y imprimirá cuantos

argumentos tipeamos.

Luego pondrá la var_4 a cero que es el contador del for la puedo renombrar a i.

Page 275: c y Reversing

Allí esta coloreado el loop se mantendrá loopeando mientras i sea menor que argc.

Vemos como toma el contador i y lo multiplica por 4 dentro del LEA, o sea que EDX se usara para

sumar al puntero, y poder recorrer todos los punteros a las strings, poniendo un BP aquí y tipeando

algunos argumentos.

Page 276: c y Reversing

En IDA en las Opciones del proceso o Process Options colocamos los argumentos y arrancamos el

debugger.

Vemos que luego de imprimir la cantidad de argumentos para en el BP, a EAX pasa el puntero al

array, vemos en el dump que el contenido de EAX o sea el primer campo, es otro puntero.

Como EDX es cero ya que es el primer ciclo del loop

mov eax, [edx+eax]

EAX tendrá el valor de dicho primer puntero que apuntara a la primera string de los argumentos.

Page 277: c y Reversing

Si le doy RUN para de nuevo en el BP y al llegar aquí

004012EF mov eax, [edx+eax]

En este caso EDX vale 4 ya que i valía 1 y lo multiplica por 4, por lo tanto le sumara al puntero al

array, cuatro lo que apuntara al segundo campo o sea al segundo puntero a la string de los

argumentos.

Page 278: c y Reversing

O sea que confirmamos que argv es un puntero a un array de punteros a strings.

Bueno quería mostrar un poco como se ingresaban argumentos por consola, ya que algunos

ejemplos los usan.

Allí van 3 ejemplos sencillos y uno mas complejo, en este ultimo aparece un tipo de datos

avanzado llamado HANDLE, se usa para casos muy específicos como el valor que devuelve

OpenProcess en este caso, si ponemos que sea un int no funcionara.

HANDLE phandle = OpenProcess(PROCESS_ALL_ACCESS,0,pid);

Allí vemos que la api esta definida de esa forma y devuelve un tipo HANDLE, el resto es conocido,

como ayuda les diré que antes de pedir el handle a OpenProcess se llama a una funcion que eleva

los privilegios de nuestro proceso, si no muchas veces no tendrá los suficientes privilegios para leer

la memoria de ciertos procesos.

Hasta la practica 2 y que se diviertan.

Ricardo Narvaja