0% found this document useful (0 votes)
44 views

Reverse Engineering

This document provides an introduction to reverse engineering using a simple example program. It discusses reverse engineering goals like investigating how a program works or finding vulnerabilities. The document outlines the reverse engineering process of disassembling an executable file to view the assembly language code and deduce the original source code. It also provides some basic context about assembly language needed to understand disassembled code. Finally, it introduces example source code for a program that will be disassembled and analyzed throughout the module to demonstrate reverse engineering techniques.

Uploaded by

John Cortez
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
44 views

Reverse Engineering

This document provides an introduction to reverse engineering using a simple example program. It discusses reverse engineering goals like investigating how a program works or finding vulnerabilities. The document outlines the reverse engineering process of disassembling an executable file to view the assembly language code and deduce the original source code. It also provides some basic context about assembly language needed to understand disassembled code. Finally, it introduces example source code for a program that will be disassembled and analyzed throughout the module to demonstrate reverse engineering techniques.

Uploaded by

John Cortez
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 58

Reverse

engineering
PID_00277609

Josep Vañó Chic


© FUOC • PID_00277609 Reverse engineering

Josep Vañó Chic

El encargo y la creación de este recurso de aprendizaje UOC han sido coordinados


por el profesor: Jordi Serra Ruiz

Primera edición: septiembre 2021


© de esta edición, Fundació Universitat Oberta de Catalunya (FUOC)
Av. Tibidabo, 39-43, 08035 Barcelona
Autoría: Josep Vañó Chic
Producción: FUOC
Todos los derechos reservados

Ninguna parte de esta publicación, incluido el diseño general y la cubierta, puede ser copiada,
reproducida, almacenada o transmitida de ninguna forma, ni por ningún medio, sea este eléctrico,
mecánico, óptico, grabación, fotocopia, o cualquier otro, sin la previa autorización escrita
del titular de los derechos.
© FUOC • PID_00277609 Reverse engineering

Índice

Introducción............................................................................................... 5

1. Reverse engineering............................................................................. 7

2. Lenguaje ensamblador...................................................................... 8

3. Código de ejemplo............................................................................. 9

4. Información general......................................................................... 12

5. Strings................................................................................................... 14

6. Variables............................................................................................... 15

7. Pila o Stack.......................................................................................... 17

8. MOV y LEA........................................................................................... 18

9. Registros............................................................................................... 19

10. Bucles..................................................................................................... 21

11. Condicionales...................................................................................... 27
11.1. Registro de estado (FLAGS) ......................................................... 27

12. Subrutinas............................................................................................ 32

13. Renombrar y personalizar............................................................... 45

14. Paso de parámetros y flujo de ejecución..................................... 48

15. Alteración del código........................................................................ 54


© FUOC • PID_00277609 5 Reverse engineering

Introducción

La ingeniería inversa de software permite obtener o deducir el código fuente


de un programa a partir del fichero ejecutable; este hecho tiene sus ventajas,
pero también puede implicar actividades malintencionadas.

Este módulo permite realizar una introducción a la ingeniería inversa a partir


de un ejemplo de un fichero ejecutable y se muestra cómo deducir el código
ensamblador para deducir cuál sería su código fuente.

Existen multitud de libros, tutoriales y videotutoriales sobre la ingeniería in-


versa, algunos de ellos de varios cientos de páginas; así pues, este módulo so-
lo pretende realizar una introducción guiada sobre cómo realizarla y detectar
algunas vulnerabilidades
© FUOC • PID_00277609 7 Reverse engineering

1. Reverse engineering

En la ingeniería inversa se trata de desarmar un objeto para ver cómo funciona


para duplicar o mejorar el objeto. Esta ha sido una práctica realizada por las
industrias más antiguas; así pues, este concepto también se ha aplicado y se
está aplicando con frecuencia en hardware y software de aplicaciones infor-
máticas en toda su gama de plataformas.

La ingeniería inversa de software implica invertir el código máquina de un


programa ejecutable a código fuente.

Objetivos de ingeniería inversa

• Investigar y descubrir cómo funciona un programa.


• Construir un programa compatible.
• Localizar funcionalidad oculta, por ejemplo, acceso a través de puertas tra-
seras.
• Buscar vulnerabilidades software ya por el uso de funciones no seguras o
por un diseño inconsistente.

Análisis

• Rastrear, observar el código para comprender cómo funciona.


• Poder generar código de alto nivel a medida que avanza.
• Observar y deducir el flujo de llamadas a las funciones.
• Determinar los tipos de datos utilizados en un programa.
• Observar los valores de los registros, pila, flags.

Pero la ingeniería inversa también tiene sus riesgos en caso de usarse para mo- Contenido
dificar código con fines malintencionados; de esta forma, los profesionales en complementario

desarrollo de software deben ser conscientes de estos riesgos y, por lo tanto, Existe una gran diversidad de
investigar cómo desarrollar software seguro –tanto las funciones a implemen- herramientas en el mercado
para realizar reverse engineering
tar como la lógica y el flujo del software– ya que si está colocando código con- a partir de desensambladores y
debugadores. Para realizar es-
fidencial en un entorno en el que un atacante puede obtener acceso, debería te módulo se han utilizado dos
herramientas gratuitas, IDA y
preocuparse por los riesgos de la ingeniería inversa o la modificación no au-
Ghidra.
torizada del código. IDA <https://www.hex-
rays.com/products/ida/sup-
port/download_freeware/>
Ghidra <https://ghi-
dra-sre.org/>
© FUOC • PID_00277609 8 Reverse engineering

2. Lenguaje ensamblador Programación en


ensamblador (x86-64)

Podéis repasar el lenguaje de


programación ensamblador en
el módulo de «Programación
en ensamblador» de la UOC:
<http://cv.uoc.edu>
Para realizar reverse engineering deberemos partir de un fichero ejecutable y, co-
mo veremos en este módulo, se trata de desensamblarlo y por lo tanto obtener
el código en lenguaje ensamblador.

Así pues, para poder llevar a cabo reverse engineering e investigar el desensam-
blado y deducir cómo se ha desarrollado el código fuente que lo ha generado,
se deben tener unos conocimientos básicos del lenguaje ensamblador (assem-
bler).

Este módulo no pretende ser una guía de aprendizaje del lenguaje ensambla-
dor, pero sí que se recomienda su lectura acompañada con la del módulo «Pro-
gramación en ensamblador (x86-64)», ya que en ese módulo se puede profun-
dizar en este lenguaje; además, podréis encontrar ejemplos ilustrativos de có-
mo se convierte el código fuente en lenguaje ensamblador. En cambio, en este
módulo el proceso es a la inversa, es decir, a partir del lenguaje ensamblador,
se deduce cómo se ha desarrollado el código fuente.

A continuación, se muestra un ejemplo simple del proceso de lenguaje C a


lenguaje ensamblador. Partimos de este simple código:

if (a > b) {
maxA = 1;
maxB = 0;
}

En el siguiente cuadro se muestra el resultado de la conversión del lenguaje


C a lenguaje ensamblador.

mov rax, qword [a] ;Se cargan las variables en registros


mov rbx, qword [b]
cmp rax, rbx ;Se realiza la comparación
jg cierto ;Si se cumple la condición, salta a la etiqueta cierto
jmp fin ;Si no se cumple la condición, salta a la etiqueta fin
cierto:
mov byte [maxA], 1 ;Estas instrucciones solo se ejecutan
mov byte [maxB], 0 ;cuando se cumple la condición
fin:
© FUOC • PID_00277609 9 Reverse engineering

3. Código de ejemplo

Aunque en reverse engineering se trata de analizar el código desensamblado, para


facilitar el seguimiento de este módulo y facilitar el aprendizaje se aporta el
código fuente. Este código se ha compilado y desensamblado posteriormente
con las herramientas IDA y Ghidra.

El programa consta de tres funciones:

• main
– Entrada principal del programa

• introducirPassword
– Introducción de la contraseña por teclado del usuario

• validationPassword
– Genera el password a través de un bucle con códigos ASCII generando
el password ABCD
– Comprueba si el password introducido por el usuario es correcto

• accesoValido
– Acceso en caso de que la contraseña introducida por el usuario sea
correcta

En la siguiente imagen se muestran los métodos accesoValido y validationPass-


word.
© FUOC • PID_00277609 10 Reverse engineering

A continuación, se muestran los métodos introducirPassword y main.


© FUOC • PID_00277609 11 Reverse engineering

Hay que tener en cuenta que este código contiene funciones vulnerables como
strcpy y strcmp; permite introducir un password de una longitud superior a la
longitud máxima de la contraseña y tampoco lo comprueba; todos estos ele-
mentos se han incluido expresamente para detectarlos en el momento de rea-
lizar la ingeniería inversa y observar las consecuencias que conlleva no aplicar
un desarrollo de programación de código seguro.
© FUOC • PID_00277609 12 Reverse engineering

4. Información general

Para realizar este módulo utilizaremos un programa sencillo, el cual se ha com-


pilado en dos versiones de 32 y 64 bits.

En primer lugar, al elegir la opción Disassemble.

Podemos obtener información básica sobre el ejecutable, por ejemplo, si se ha


compilado en versión de 32 o 64 bits.

Información de un ejecutable compilado en 32 bits.


© FUOC • PID_00277609 13 Reverse engineering

En la versión del mismo programa compilado en 64 bits.


© FUOC • PID_00277609 14 Reverse engineering

5. Strings

Una de las posibilidades que disponemos al haber desensamblado el programa


es la posibilidad de visualizar los textos que se han asignado a los Strings.

Esto nos da la posibilidad de poder observar los mensajes y otros textos; por
ejemplo, si se hubiera asignado una contraseña de tipo puerta trasera, o una
contraseña preestablecida a un String, nos aparecería visualizada; esto no quie-
re decir que una contraseña no se pueda asignar a una variable de otro modo;
de hecho, en el programa de ejemplo se ha asignado una contraseña a una
variable, pero a base de códigos ASCII, de forma que se puede llegar a deducir
realizando reversing.

En la próxima imagen se puede observar la lista de literales que se han imple-


mentado en la aplicación.
© FUOC • PID_00277609 15 Reverse engineering

6. Variables

Para poder observar los valores de las variables, se puede obtener la vista de la
pila de las variables haciendo doble clic en cualquiera de ellas; recordad que,
normalmente, estas variables se utilizan como valor de desplazamiento en la
memoria, como se ha podido observar en el ejemplo.

En este contexto disponemos de varias opciones a través del menú contextual,


por ejemplo, convertir en array.

En la imagen siguiente se muestran los parámetros disponibles para la conver-


sión a array.
© FUOC • PID_00277609 16 Reverse engineering

Cuyo resultado sería:


© FUOC • PID_00277609 17 Reverse engineering

7. Pila o Stack

La pila es una sección de la memoria que permite almacenar en un modo de


acceso LIFO, es decir, el último en entrar es el primero en salir. De forma que solo
se tiene acceso a la parte superior de la pila.

Para el manejo de los datos de la pila existen dos operaciones, apilar o PUSH,
que coloca un objeto en la pila, y su operación inversa, recoger o POP, que
retira el último elemento apilado.

En el siguiente ejemplo, al acceder a la función introducirPassword, se guarda


el valor almacenado en el registro ebp en la pila.

En la ejecución del programa podremos observar los valores de la pila en la


ventana Stack view.
© FUOC • PID_00277609 18 Reverse engineering

8. MOV y LEA

Para comprender mejor el funcionamiento del programa, a continuación des-


cribiremos las diferencias entre estas dos instrucciones.

mov destino, fuente.

Copia el valor del operando fuente sobre el operando destino, sobrescribien-


do el valor original del operando destino. El operando destino puede ser un
registro de propósito general, un registro de segmento o una dirección de me-
moria. El operando fuente puede ser un valor numeral, un registro de propó-
sito general, un registro de segmento o una dirección de memoria. Se debe
cumplir la condición de que los dos operandos sean del mismo tamaño, sea
byte, word, dword o qword.

lea destino, fuente.

Asigna la dirección de memoria del operando fuente en el registro del operan-


do destino. El operando destino debe ser un registro de propósito general.
© FUOC • PID_00277609 19 Reverse engineering

9. Registros

Un registro es una zona en la memoria del procesador donde se almacena un


valor único. Hay que tener en cuenta que solo existe un conjunto determinado
de registros, cada uno con un propósito específico.

En arquitecturas x86 de 32 bits podemos encontrar los registros de propósito


general siguientes:

• EAX (Extended Accumulator Register). Se utiliza como contenedor para re-


solver operaciones matemáticas simples o como registro de propósito ge-
neral.
• EBX (Extended Base Register) – Registro de propósito general.
• ECX (Extended Counter Register) – Registro utilizado generalmente como
contador en un bucle.
• EDX (Extended Data Register) – Registro de propósito general, usado tam-
bién como parámetro en funciones o direccionar datos en memoria.
• ESI (Extended Source Index) – Registro generalmente utilizado como punte-
ro. Es utilizado en funciones que requieren un origen y un destino para
los datos que se utilizan almacenando el origen.
• EDI (Extended Destination Index) – Igual que el registro ESI, usado como
puntero, en este caso apuntando al destino.
• EBP (Extended Base Pointer) – Registro utilizado como puntero a una direc-
ción de memoria, también puede ser utilizado como registro de propósito
general.
• ESP (Extended Stack Pointer) – Almacena un puntero a la parte superior de
la pila.
• EIP (Extended Instruction Pointer) – Puntero de instrucciones. Contiene el
contador del programa, la dirección de la próxima instrucción.

En la arquitectura de 64 bits, los registros de propósito general son:

• 16 registros de datos RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP y R8-R15.
• La denominación de ocho primeros registros es parecida a los 8 registros
de propósito general de 32 bits (EAX, EBX, ECX, EDX, ESI, EDI, EBP y ESP).

Los registros son accesibles de la forma siguiente:

• Como registros de 64 bits (quad word). (En la arquitectura de 64 bits).


• Como registros de 32 bits (double word), accediendo a los 32 bits de menos
peso.
• Como registros de 16 bits (word), accediendo a los 16 bits de menos peso.
© FUOC • PID_00277609 20 Reverse engineering

• Como registros de 8 bits (byte), permitiendo acceder individualmente a


uno o dos de los bytes de menos peso según el registro.

Tamaño de los operandos:

Un operando puede ser de los tipos: byte, word, double word y quad word. Hay
que tener en cuenta que lo hace en formato little-endian.

• BYTE: el tamaño del operando es de un byte (8 bits).


• WORD: el operando es de una palabra (word) o dos bytes (16 bits).
• DWORD: el operando es de una palabra doble (double word) o cuatro bytes
(32 bits).
• QWORD: el operando es de una palabra cuádruple (quad word) u ocho
bytes (64 bits).
© FUOC • PID_00277609 21 Reverse engineering

10. Bucles

En primer lugar, debemos tener en cuenta que IDA asigna nombres a las va-
riables en función de su ubicación en relación con la dirección de retorno. Las
variables locales se encuentran por encima de la dirección de retorno, mien-
tras que los parámetros de la función se encuentran debajo de la dirección
de retorno. Los nombres de las variables locales se derivan usando el prefijo
var_ unido con un sufijo hexadecimal; eso indica la distancia, en bytes: que
la variable se encuentra por encima del puntero. Por ejemplo, la variable local
var_C, en este caso, es una variable de 4 bytes (dword) que se encuentra -12 by-
tes (-0Ch tiene el valor -12 en decimal) por debajo del puntero ([ebp-0Ch]).
Los nombres de los parámetros de las funciones se generan utilizando el pre-
fijo arg_ combinado con un sufijo hexadecimal que representa la distancia
relativa desde el parámetro superior.

A continuación, localizaremos o deduciremos el código siguiente:

En primer lugar, observamos el flujo general del diagrama para localizar los
bucles implementados en la aplicación.
© FUOC • PID_00277609 22 Reverse engineering

La herramienta dispone de una opción para poder observar el código en modo


texto en lugar de modo gráfico.

Una vez elegida la opción mostrada en la imagen anterior, dispondremos del


código en modo texto.
© FUOC • PID_00277609 23 Reverse engineering

A continuación, realizamos reversing de un bucle:

En primer lugar, se almacena (mov) el valor 65 (41h, en hexadecimal), y se


dirige a la etiqueta loc_401534; luego se compara (cmp) el valor 65 con el
valor 68 (44h, en hexadecimal), se analiza el resultado con jle, compara si
es menor o igual; en este caso cabe observar que el compilador ha convertido
i<69 en i<=68; en caso que se cumpla, se dirige a la etiqueta loc_401523.

El resto del código se puede deducir; por ejemplo, nuestra variable i es [ebp
+var_C]; por ejemplo, i++ corresponde a: add [ebp+var_C], 1 a conti-
nuación de esta línea vuelve al principio del bucle loc_401537.
© FUOC • PID_00277609 24 Reverse engineering

El código sub eax, 41h corresponde a la operación i-65 cuyo resultado se


almacena en el registro eax y el código  mov [ebp+eax+var_1C], dl   equi-
vale a  pass[i-65] = i ya que en [ebp+eax+var_1C] está almacenado
el resultado de i-65.

Una vez finalizado el bucle continúa en mov [ebp+var_18], 0, que corres-


ponde a nuestro código pass[4]=0.

Analizando el código anterior se puede deducir que se trata de un bucle rea-


lizado con la instrucción for, ya que, como se ha podido observar, se ha
seguido la estructura siguiente:

mov ;Asignar valor inicial a un contador


etiqueta:
jle ;Condición if, comprueba si el valor inicial es inferior a un valor máximo en caso contrario
se sale del bucle
instrucciones de proceso
add ;suma 1 al contador
ir a la etiqueta

Aunque también podría haberse realizado con la instrucción while, el con-


dicional de la instrucción While no tiene por qué estar relacionado con el
de un valor de un contador, sino que puede ser en función del resultado de
cualquier condicional.

A continuación, analizaremos el bucle do While, que se encuentra en la


función introducirPassword.

La característica principal de la instrucción do While es que, como mínimo,


el bucle se ejecuta una vez y que el condicional se encuentra al final del bucle
en lugar de al inicio.
© FUOC • PID_00277609 25 Reverse engineering

En la función introducirPassword el bucle empieza en loc_401596 y, como se


puede observar, no hay ningún condicional al inicio del bucle.
© FUOC • PID_00277609 26 Reverse engineering

En la siguiente imagen se puede realizar la comparación con el código fuente:

En la parte final del bucle podemos observar el condicional JNZ, que en reali-
dad comprueba el valor del resultado del retorno de la función validationPass-
word; tampoco existe un contador sobre el cual haya ninguna condición de
seguir en el bucle; por lo tanto, podemos deducir que se trata de una instruc-
ción do While.

En la siguiente imagen se puede realizar la comparación con el código fuente:


© FUOC • PID_00277609 27 Reverse engineering

11. Condicionales

Para poder realizar reversing de los condicionales, en primer lugar, repasaremos


los conceptos básicos.

11.1. Registro de estado (FLAGS)

FLAGS es un registro que contiene los diferentes indicadores o banderas refe-


rentes al estado del proceso, es decir, contiene información sobre el estado del
procesador e información sobre el resultado de la ejecución de las instruccio-
nes. A continuación, se muestra la lista de flags.

• OF: Overflow flag (bit de desbordamiento)


• TF: Trap flag (bit de excepción)
• AF: Aux carry (bit de transporte auxiliar)
• DF: Direction flag (bit de dirección)
• SF: Sign flag (bit de signo)
• PF: Parity flag (bit de paridad)
• IF: Interrupt flag (bit de interrupción)
• ZF: Cero flag (bit de cero)
• CF: Carry flag (bit de carga)

En la herramienta IDA los valores de los flags los podemos encontrar en la


ventana de General registers.

• Salto�incondicional:
– JMP: salta de manera incondicional

• Saltos�condicionales, saltos que consultan el valor del flag una vez reali-
zada una operación:
– JA: salta si CF=0 y ZF=0 (jump if above).
– JAE: salta si CF=0 (jump if above or equal).
© FUOC • PID_00277609 28 Reverse engineering

– JB: salta si CF=1 (jump if below).


– JBE: salta si CF=1 o ZF=1 (jump if below or equal).
– JC: salta si hi hay carga CF=1 (jump if carry).
– JCXZ: salta si CX=0 (jump if CX=0).
– JECXZ: salta si ECX=0 (jump if ECX=0).
– JE: salta si ZF=1 (jump if equal).
– JG: salta si ZF=0 y SF=OF (jump if greater).
– JGE: salta si SF=OF (jump if greater or equal).
– JL: salta si SF<>OF (jump if less).
– JLE: salta si ZF=1 o SF<>OF (jump if less or equal).
– JNA: salta si CF=1 o ZF=1 (jump if not above).
– JNAE: salta si CF=1 (jump if not above or equal).
– JNB: salta si CF=0 (jump if not below).
– JNBE: salta si CF=0 y ZF=0 (jump if not below or equal).
– JNC: salta si CF=0 (jump if not carry).
– JNE: salta si ZF=0 (jump if not equal).
– JNG: salta si ZF=1 o SF<>OF (jump if not greater).
– JNGE: salta si SF<>OF (jump if not greater or equal).
– JNL: salta si SF=OF (jump if not less).
– JNLE: salta si ZF=0 i SF=OF (jump if not less or equal).
– JNO: salta si OF=0 (jump if not overflow).
– JNP: salta si PF=0 (jump if not parity).
– JNS: salta si SF=0 (jump if not sign).
– JNZ: salta si ZF=0 (jump if not zero).
– JO: salta si OF=1 (jump if overflow).
– JP: salta si PF=1 (jump if parity).
– JPE: salta si PF=1 (jump if parity even).
– JPO: salta si PF=0 (jump if parity odd).
– JS: salta si SF=1 (jump if sign).
– JZ: salta si ZF=1 (jump if zero).

En la siguiente imagen se puede observar un ejemplo de salto incondicional:


© FUOC • PID_00277609 29 Reverse engineering

En la siguiente imagen se muestra en modo texto; se puede observar que el


salto es debido a la existencia de un bucle y, por lo tanto, se ha debido organizar
el código y las etiquetas para poderse implementar.

Para mostrar los opcodes se puede activar a través de la ventana de opciones;


en este caso indicamos:

Number of opcode bytes (non-graph): 6

En la siguiente imagen se muestra el código con los opcodes:


© FUOC • PID_00277609 30 Reverse engineering

Podemos observar el opcode EB que corresponde a jmp y que saltará 11


posiciones (11 hexadecimal, es decir, 17 en decimal) más 2 bytes que ocupa
la instrucción; esta operación nos da el resultado de 0x401534, que es la
dirección a la cual se dirigirá.

En la siguiente imagen se muestra el bucle, a la vez que se puede observar el


salto condicional jle que redirige el flujo de la ejecución en función del re-
sultado de la comparación entre dos valores realizada por la instrucción cmp.

En la siguiente imagen se puede observar el salto condicional jz y el salto


incondicional jmp.

Cabe destacar que en el modo gráfico de la herramienta IDA, en los saltos con-
dicionales muestra en color verde la línea del flujo en caso de que se cumpla
la condición, y en rojo en caso contrario.
© FUOC • PID_00277609 31 Reverse engineering

En la imagen siguiente se muestra en modo texto:


© FUOC • PID_00277609 32 Reverse engineering

12. Subrutinas

Para hacer la llamada a la subrutina se utiliza la instrucción call seguido del


nombre de la etiqueta que define el punto de entrada a la subrutina, por ejem-
plo: call _validationPassword.

Para conocer la relación entre los nombres de las etiquetas y las direcciones de
memoria abriremos el panel Names Window.
© FUOC • PID_00277609 33 Reverse engineering

Obteniendo la lista de métodos o funciones obtenemos una valiosa informa-


ción, ya que podemos observar la estructura de funciones y los puntos donde
se llaman o acceden a estos métodos, pues podemos localizar las instrucciones
call.

Panel Names window.

Que además se puede comprobar en la vista del código en modo texto y tam-
bién en la opción Edit Function.

La instrucción call almacena en la pila la dirección de retorno, es decir, la


dirección de memoria donde se encuentra la instrucción siguiente de la ins-
trucción call, a la que se retornará después de la ejecución del salto a la fun-
ción, y a continuación transfiere el control del programa a la subrutina.

Para observar el proceso añadiremos breakpoints en el código para que la eje-


cución del programa realice una pausa en los puntos de interrupción que in-
diquemos.
© FUOC • PID_00277609 34 Reverse engineering

En este caso se ha añadido un punto de interrupción en la llamada a la función


validationPassword, que se encuentra en el método introducirPassword.

Añadiremos otro punto de interrupción en la instrucción de retorno de la fun-


ción validationPassword.

La instrucción retn retornará la ejecución del flujo del programa en la línea


siguiente de la instrucción call que ha llamado a la función; en este caso a
la dirección 0040161E.

Para observar su funcionamiento, ejecutaremos la aplicación e introduciremos


como password el valor 123456.
© FUOC • PID_00277609 35 Reverse engineering

A continuación, el programa se detendrá en el punto de interrupción que he-


mos indicado:

Una vez detenido podemos observar, por ejemplo, los valores de los registros;
en la siguiente imagen observamos el valor del registro eax, en el que pode-
mos observar que contiene el valor que se ha introducido por teclado.

En el registro esp se guarda la dirección de memoria en la que se encuentra


el valor 123456.

Como podemos comprobar en el volcado de memoria, en la posición


0061FE04 y siguientes, tenemos los valores en hexadecimal 31, 32, 33, 34, 35
y 36, que, convertidos en decimal, equivalen a los valores 49, 50, 51, 52, 53 y
54, que son los valores en ASCII de los números 1, 2, 3, 4, 5 y 6.
© FUOC • PID_00277609 36 Reverse engineering

También podemos observar que en la dirección 0061FDF0 está el puntero a


la dirección 0061FE04.

En la ventana de General registres se pueden encontrar los valores de los regis-


tros en el momento de la pausa de la ejecución en el punto del breakpoint.

También resulta útil observar los valores de la ventana de la pila.

Como se ha podido observar en una imagen anterior, la instrucción siguiente


a la llamada a la función validationPassword se halla en la dirección 00401619,
es decir, cuando se ejecute la instrucción retn ubicada en la función valida-
© FUOC • PID_00277609 37 Reverse engineering

tionPassword, el flujo de la ejecución debe seguir en la posición 0040161E.


Recordemos que esta dirección corresponde a una instrucción de la función
introducirPassword.

Existen dos opciones para seguir con la ejecución del programa paso a paso
por cada instrucción.

• Step Over : con esta opción el programa ejecutaría la función validation-


Password, pero se detendría en la dirección 0040161E y no podríamos ob-
servar los pasos realizados dentro de la función validationPassword.

• Step into : en este caso, el programa se detiene en la primera instrucción


de la función validationPassword. En este ejemplo de la ejecución del pro-
grama se ha elegido esta opción, de forma que el flujo que ha detenido en
la dirección 00401514 ya dentro de la función validationPassword.

Observemos en este punto los valores de la pila en la que se ha almacenado la


dirección de retorno 0040161E donde se encuentra la siguiente instrucción
de call _validationPassword.

Y los valores de los registros.


© FUOC • PID_00277609 38 Reverse engineering

A continuación, ejecutamos la siguiente instrucción eligiendo la opción Step


Over .

Como se ha ejecutado la instrucción push ebp, se ha añadido a la pila el valor


de ebp, como se puede observar en la imagen siguiente.

Luego, con la opción Continue Process , la ejecución continúa y se detiene en


el siguiente Breakpoint que hemos indicado.

En este punto se puede observar el valor de ESP en el que se almacena el pun-


tero en la parte superior de la pila; su valor es 0061FDEC, que es el puntero
de la dirección 0040161E, que a su vez es la dirección de retorno, es decir, la
instrucción siguiente a la llamada call validationPassword.
© FUOC • PID_00277609 39 Reverse engineering

Podemos observar estos valores en la ventana de Registros.

Así como la ventana de la pila, tal y como se muestra en la siguiente imagen.

¿Qué ocurriría si en lugar de haber introducido por teclado el valor 123456 se


hubiera introducido el siguiente valor?

Es decir, treinta y un caracteres.

En este caso, al llegar a la instrucción de retorno de la función validationPass-


word.

En lugar de tener almacenado en la pila, en la dirección 0061FDEC la dirección


de retorno 0040161E.
© FUOC • PID_00277609 40 Reverse engineering

En este caso los valores serían los siguientes:

Es decir, se ha sobrescrito en la pila el valor de la dirección 0061FDEC con el


valor 31303938 en lugar de la dirección real de retorno 0040161E.

Recordemos que los valores en hexadecimal 31 30 39 38 corresponden a los


valores en decimal 49 48 57 56, que son los valores en ASCII de 1098, es
decir, los últimos 4 caracteres de los 31 que se han introducido por teclado,
pero en sentido inverso.

Observemos también los valores de los registros; el valor de ebp o rsp conti-
núa siendo la dirección del puntero prevista donde se debía almacenar la di-
rección de retorno.

Observemos, también, que en el volcado de memoria se han sobrepasado los


10 caracteres previstos y definidos en el código fuente, char buffer[10];
© FUOC • PID_00277609 41 Reverse engineering

A continuación, al ejecutarse la instrucción retn de la función validationPass-


word, en lugar de recoger el valor de retorno 0040161E, ha recogido como
valor de retorno 31303938 que obviamente no es correcta, por lo que la eje-
cución del programa lanza el siguiente error.

Y como consecuencia, IDA muestra también el mensaje siguiente:

¿Por qué ha ocurrido la sobreescritura del valor de retorno en la pila?

Esto ha sido debido al uso de instrucciones no seguras, como strcpy o


strcmp, entre otras.

En el código fuente de este ejemplo, en la función validationPassword, en pri-


mer lugar, se ha definido:

char buffer[10];

Y a continuación se ha implementado la instrucción

strcpy(buffer, password);
© FUOC • PID_00277609 42 Reverse engineering

La función strcpy copia la cadena password (incluyendo el carácter nulo) a


la cadena buffer. El principal problema de esta función es cuando la cadena
origen tiene un tamaño mayor a la cadena destino; en este ejemplo la cadena
buffer, ya que en este caso se sobrescribirá la memoria fuera del buffer, en
este caso de la pilla, pudiendo dar pie a ataques de tipo buffer overflow como
hemos visto.

La solución a este tipo de problema es utilizar por ejemplo la función strncpy,


la cual incluye un parámetro para indicar la cantidad de caracteres máximos
que pueden ser copiados. Así pues, en el programa de ejemplo se debería haber
codificado de la forma siguiente:

strncpy(buffer, password, sizeof(buffer));

Una de las posibilidades que nos permite el reverse engineering es la localización


de vulnerabilidades.

Recordemos que existen una gran diversidad de funciones vulnerables, por


ejemplo, gets,�getwd,�strcpy,�strcat,�sprintf,�scanf,�sscanf,�fscanf,�vfscanf,�vs-
printf,�vscanf,�vsscanf,�streadd,�strecpy,�realpath,�syslog,�getopt,�getopt_long,
getpass, realpath, etc. La mayoría de estas funciones pueden provocar situa-
ciones de buffer�overflow�si los valores de los parámetros que se asignan no
son correctos o están sobredimensionados.

Una de las opciones que disponen los debugadores es localizar funciones; en


el caso de IDA, podemos comprobar la existencia de funciones vulnerables a
través de la ventana Names window.

En este ejemplo:

A través de esta ventana hemos observado que se utilizan las funciones vul-
nerables strcpy y strcmp.

A partir de este punto podemos localizar dónde se encuentran las funciones


vulnerables a través de la opción Search/text.
© FUOC • PID_00277609 43 Reverse engineering

En primer lugar, buscaremos dónde se encuentra la llamada a la función


strcpy.

Y en este ejemplo obtendremos el resultado, en el que nos indica que se en-


cuentra una llamada a la función strcpy en la función validationPassword.

Realizando una búsqueda de la función strcmp.


© FUOC • PID_00277609 44 Reverse engineering

El resultado que se muestra nos indica que la llamada a la función strcmp se


encuentra en las funciones validationPassword y en introducirPassword.

De forma que a través de la ventada del resultado de la búsqueda se puede


acceder directamente a la ubicación de la llamada a la función, en este caso
strcmp.
© FUOC • PID_00277609 45 Reverse engineering

13. Renombrar y personalizar

A medida que realizamos la ingeniería inversa y vamos localizando bucles,


secuencias, condicionales, arrays, etc., es conveniente renombrar los nombres
de las etiquetas y variables.

En el siguiente ejemplo hemos localizado un bucle en el que se crea el pass-


word de entrada a través de un bucle concatenando valores. Así pues, pode-
mos personalizar la etiqueta loc_401523 para que, al revisar el código desen-
samblado, sea más ágil.

Con el botón derecho accedemos a diversas opciones, entre las cuales se en-
cuentra Rename.

A continuación, introducimos el nuevo nombre a la etiqueta:


© FUOC • PID_00277609 46 Reverse engineering

Automáticamente se renombrarán todas las referencias a loc_401523 por el


nuevo nombre bucle_crear_Password.

Este proceso se puede realizar para otros elementos, por ejemplo, las variables.
En este caso se ha detectado que var_17 es un array o el puntero de un array en
el que se almacena el valor que ha introducido el usuario por teclado; así pues,
renombraremos el nombre de la variable y le pondremos de nombre buffer.

Entrada del nuevo nombre de la variable en la pila:

Como se puede observar, se ha renombrado el nombre de la variable:

Así como en todas las referencias a la variable.


© FUOC • PID_00277609 47 Reverse engineering

Y en la pila de las variables:


© FUOC • PID_00277609 48 Reverse engineering

14. Paso de parámetros y flujo de ejecución

Para observar el proceso del paso de parámetros añadiremos un breakpoint en


el punto de la llamada a la función validationPassword que se encuentra en la
función introducirPassword.

Una vez se ha introducido una contraseña por teclado, la ejecución se detiene


en el punto de la llamada a la función validationPassword.

Como podemos observar, el valor 123456 que se ha introducido por teclado


se encuentra en la dirección 0061FE04.

Cuyo puntero a esta dirección de memoria está almacenado en el registro EAX.


© FUOC • PID_00277609 49 Reverse engineering

Y en el ESP tenemos almacenado el puntero en la parte superior de la pi-


la 0061FDF0 que, en este momento, contiene el puntero en la dirección
0061FE04.

Con la opción Step into ejecutaremos paso a paso las instrucciones de la


función validationPassword.

Como podemos observar, nada más entrar en la función validationPassword


es que la llamada call validationPassword ha almacenado en la pila la
dirección de retorno 0040161E a la que se tiene que dirigir la instrucción retn
al finalizar el flujo en la función validationPassword.

A continuación, podemos observar que la instrucción push ha almacenado el


valor de ebp en la pila para recuperarlo posteriormente, ya que la siguiente
instrucción mov cambiará el valor de ebp.
© FUOC • PID_00277609 50 Reverse engineering

A continuación, podemos observar su valor en la ventana de la pila.

El flujo continuará realizando un bucle en la etiqueta renombrada


bucle_crear_Password, que ya hemos analizado en el apartado en que hemos
tratado los bucles; una vez finalizado el flujo en este bucle, la ejecución con-
tinúa en el siguiente punto:

Donde en la segunda línea asignará al registro eax el puntero de la dirección


0061FE04; recordemos que en esta posición está almacenado el inicio del va-
lor que se ha introducido por teclado 123456 y que se ha pasado como pará-
metro a la función validationPassword.
© FUOC • PID_00277609 51 Reverse engineering

Luego se ha almacenado esp+4 el puntero 0061FDB4 que contiene la direc-


ción 0061FE04 con el valor 123456.

De forma que en la llamada call strcpy almacenará el valor del parámetro


en el array buffer.

Que corresponde al código fuente:

strcpy(buffer, password);

Una vez ejecutada la instrucción call strcpy ya tenemos almacenado el


parámetro en el array buffer.

Observemos que en la dirección ebp+var_1C está almacenado el puntero a la


dirección 0061FDCC, que es donde se encuentra el valor ABCD que ha gene-
rado el bucle bucle_crear_Password.
© FUOC • PID_00277609 52 Reverse engineering

41 42 43 44 son los valores en hexadecimal de 65 66 67 68 que, a su vez,


son los códigos ASCII de ABCD.

En el momento de ejecutar la llamada call strcmp, en esp+4 tenemos el


puntero a la dirección 0061FDCC, que es donde se encuentra el valor ABCD.

En esp tenemos el puntero a la dirección 0061FDD1, que es el inicio de la


dirección donde se encuentra el valor 123456.

Así pues, la llamada call strcmp comparará el valor 123456 con ABCD.

Como los dos valores no son iguales al realizar la comparación el resultado


del flag ZF tiene el valor 1. Tal y como se muestra en la siguiente imagen,
en este caso, el flujo se dirigirá a la etiqueta loc_401577 y no tendrá acceso
a la función accesoValido.
© FUOC • PID_00277609 53 Reverse engineering
© FUOC • PID_00277609 54 Reverse engineering

15. Alteración del código

Algunos debugadores permiten realizar la alteración del código desensambla-


do cambiando todo tipo de valores y así cambiar el flujo de la aplicación; en
nuestro caso, realizaremos esta operación como parte del reverse engineering
para comprobar hasta qué punto el programa es vulnerable y puede ser objeto
de ataques malintencionados.

Esta operación, en este ejemplo, la realizaremos con la herramienta Ghidra.


Una vez abierto el ejecutable, localizaremos la función validationPassword de
una forma muy parecida a la que se ha realizado con la herramienta IDA.

Una vez localizada la función validationPassword:

Observando el código desensamblado, observamos que la sección en la que


se comprueba si el password introducido es correcto es la que se muestra a
continuación.

Comparando con el código fuente:


© FUOC • PID_00277609 55 Reverse engineering

Donde

Corresponde a

La instrucción JZ analiza el valor del flag en el que se almacena el resultado


de la comparación entre el password definido en el programa y el password
introducido por el usuario. En este caso, si el valor del flag ZF es 1 el flujo
de la ejecución seguirá en la dirección de memoria en la que está ubicada la
etiqueta LAB_00401577, es decir, si el password introducido por el usuario
es incorrecto, el flujo de la ejecución del programa se dirigirá a la dirección
00401577.

Pero si cambiamos la instrucción JZ por JNZ, en este caso, al introducir una


contraseña incorrecta, seguirá el flujo de la ejecución del programa como si se
hubiera introducido una contraseña correcta, ya que la instrucción JNZ com-
prueba si el valor del flag ZF tiene el valor cero.

Otra opción también posible es, en lugar de cambiar la instrucción JZ por JNZ,
cambiar la dirección de memoria a la que debe dirigirse el flujo de la ejecución
del programa en caso de que el flag ZF tenga el valor 1.

Para modificar la instrucción JZ nos situamos en la instrucción y elegimos la


opción Patch Instruction.
© FUOC • PID_00277609 56 Reverse engineering

Como se puede observar, permite la modificación de la instrucción y la de la


dirección de memoria.

En este ejemplo modificaremos la dirección de memoria, de forma que sea el


que sea el resultado de la comparación, el flujo de la ejecución seguirá en la
dirección 00401570 y, por lo tanto, se accederá a la función accesoValido.

En este caso la herramienta ha añadido también la etiqueta LAB_00401570.

Una vez realizados los cambios podemos exportar el programa como un pro-
grama ejecutable.
© FUOC • PID_00277609 57 Reverse engineering

Comprobamos la ejecución con una contraseña incorrecta:

Como se puede observar hemos accedido al programa con una contraseña


incorrecta.

You might also like