Hace unas semanas, estuvimos reconociendo estructuras comunes en ingeniería reversa (parte I, parte II y bucles). Con esas entradas del blog ya podemos tener una buena idea de cómo encarar el reversing sin volvernos locos, pero hoy quiero presentarles otra estructura más: los condicionales de selección múltiple. Si bien estos no son tan comunes, tienen ciertas particularidades que vale la pena destacar.
En general, este tipo de estructura condicional elige uno de varios caminos alternativos de ejecución en un programa, en base a una comparación contra el valor de una variable o expresión. Esto otorga cierta claridad al programador, ya que, cuando el código a ejecutar puede tomar muchos caminos, la utilización de condicionales simples anidados puede volverse tediosa.
En la siguiente imagen se muestra un sencillo programa en C con una estructura condicional múltiple:
Se observa que si la variable i toma el valor 1, se mostrará “i = 2” por pantalla; si el valor es 2, se mostrará “i = 4”, y así podría seguir sucesivamente si hubiese más opciones. En el caso de que no se cumpla ninguna de las condiciones, podrían ejecutarse otras acciones. Asimismo, el código mostrado produce el siguiente resultado en ensamblador:
Podemos ver que estas estructuras condicionales no son muy difíciles de identificar en el código. Se observa una serie de comparaciones y saltos condicionales (marcados con color rojo), y luego un salto incondicional (a la sección default, en caso de que fallen todas las condiciones). Es decir que tenemos primero las comparaciones y saltos, y luego las secciones de código correspondientes a cada condición.
Sin embargo, cuando la cantidad de comparaciones a realizar es elevada, el compilador optimiza el código mediante la utilización de jump tables. En estos casos, el código generado no es tan directo, según se observa en la siguiente imagen:
En la esquina superior izquierda hemos resaltado la parte del código en que se verifica si la variable a comparar posee un valor mayor a la cantidad de opciones con que se cuenta. En ese caso, y para evitar realizar todas esas comparaciones innecesariamente, se va directamente al caso default, como se ve en la esquina superior derecha de la imagen.
Sin embargo, si el valor de la variable se encuentra dentro de nuestras opciones, la dirección de salto se construye a partir de una dirección base, sumada a un desplazamiento basado en el número de opción. En este sentido, y para este caso en particular, en la dirección de memoria 0x401088 se encuentra la dirección base. Allí se encuentra un arreglo de direcciones de 4 bytes, donde cada una de ellas apunta a la porción de código de su respectiva alternativa.
Vemos que el uso de jump tables permite que se haga siempre una sola comparación, pero puede ser un poco más difícil de entender al realizar reversing.