Scripting con IDAPython – Creamos, creativos y crecemos

Buenos días a todos! Tenía ganas de escribir esta entrada para poder introduciros en el mundo del Reversing aunque desde hace tiempo ya ofrecí ese «input» más concretamente el verano pasado. Os recomiendo la serie de entradas sobre Reversing desde 0 «Diseccionando binarios»

Muchos ya habrán visto los cuatro vídeos que grabe con respecto al desarrollo de scripts para reversing usando la API de IDA, pero me gustaría de manera escrita hacer un breve repaso de los hitos principales del desarrollo con el fin de que se me haya olvidado comentar algo o alguna idea que se me haya ocurrido, reflejarla en esta entrada. Por ello no me voy a detener a explicar con detalle ya que eso ya esta en los vídeos que podéis encontrar aquí

El código fuente usado en los scripts podéis encontrarlo en GitHub

Para ello he usado y usaré los binarios del curso de reversing con IDA de Ricardo Narvaja de Cracklatinos (CLS), lo cual recomiendo seguir los videos más su curso para mejora en el entendimiento. Más información aquí

Entonces os preguntaréis, ¿qué motivo existe en crear scripts orientados al reversing usando la API de IDA? El motivo principal es:

  1. Mejora el aprendizaje y entendimiento del reversing, haciendo crecer y avanzar nuestro conocimiento debido a que profundizamos y nos detenemos más en el estudio de un binario en particular.
  2. Nos permite mejorar nuestra creatividad y poder crear scripts según nuestras necesidades.
  3. Si tenemos imaginación podemos identificar patrones en el desensamblado del binario, y automatizar nuestras tareas de reversing.
  4. Una vez identificado los patrones, podemos crear clases y métodos en nuestro script para poder usarse de manera general. Es directamente proporcional a todos los binarios que apliquemos reversing.

Directos a la solución

En el primer video 0x01 me centre únicamente en la resolución del crackme test_reverser.exe creando tres funciones:

  • debug(): Nos permite correr el debugger y colocar un breakpoint en una dirección de memoria pasada por argumento e ir hacia esa dirección de memoria.

  • sum(): Nos permite obtener la suma byte a byte del user introducido mediante el uso de un bucle. Primero obtiene el valor del registro EAX siendo el valor de retorno de la función strlen, coincidiendo con el número de caracteres del user introducido. Seguidamente para poder recorrer todo el bucle del desensamblado lo creo de igual modo en python e iterando hasta el último byte del user y luego ir hacia la dirección de memoria especificada por argumento. La finalidad es obtener el valor de la sumatoria byte a byte una vez haya pasado por todos ellos e ir hacía la dirección de memoria especificada.

  • check(): Obtenemos el valor de la password correcto según el usuario introducido. Nos dirigimos dentro de la función que checkea si la password es correcta comparando el registro EAX*2 siendo la password introducida por el usuario y el valor sumatoria. Si sabemos que el valor password introducido es EAX, luego EAX vale el doble (debido a shl eax,1) y también sabemos el valor de sumatoria, es despejar la ecuación dividiendo sumatoria/2.

Clases y métodos

Una vez visto el primer video donde nos centramos exclusivamente en la resolución, preferí crear nuevas clases y métodos orientado a la recolección de información del binario (strings,segmento, funciones, llamadas etc…), descubrimiento mediante algún patron que nosotros creamos interesante y finalmente solución particular al crackme cruehead.exe.

Es muy interesante si vamos creando nuevas clases y métodos siendo creativos y echándole imaginación a todos los binarios que apliquemos reversing, podamos encontrar patrones similares a fin de poder automatizar nuestras tareas. En dos crackmes realizados hasta la fecha de hoy usando la api de IDA, he podido identificar ciertos patrones para poder crear dos clases (recon y discover) facilitándome la tarea de ingeniería inversa.

Como acabo de decir solo dos crackmes y estamos empezando únicamente 4 videos, la idea es seguir e ir mejorando el código poco a poco al igual que vuestro feedback es interesante para poder mejorar y aprender todos de todos 🙂

En la primera clase recon() tenemos los siguientes métodos. Ver video 0x02

  • segments(): Nos permite obtener los segmentos
  • functions(): Obtenemos todas las funciones del binario
  • strings(): Visualizamos las strings y su dirección de memoria
  • disamFunction(addr): Desensamblado de la función de una dirección de memoria dada o de interés nuestro.
  • disamString(addr): Desensambla string .data y su referencia a .text o code.
  • FindFunction(addr): Encuentra una función dada una dirección de memoria.
  • FindString(string): Encuentra una string dada por argumento, y desensambla todas las coincidencias de esa string en el binario mostrando el segmento donde se encuentra.

En la segunda clase discover(), intentaremos descubrir «guessing» mediante strings, llamadas o saltos condicionales información interesante para poder solucionar este crackme. Ver video 0x03

  • staticString(string): Nos muestra el desensamblado de código de la string dada por argumento y su dirección de memoria. Seguidamente nos mostrará a que función pertenece esa instrucción del segmento código de la string y la referencia a la llamada de esa función (call func), y finalmente una referencia a una localización por un salto condicional que podrá ser o no decisivo para la resolución de este crackme. Una idea que me genero esto es probar en todas las funciones sus llamadas y referencias de manera aleatoria desde el vector de entrada en este caso una string, para descubrir instrucciones que puedan ser decisivas en los cambios de flujo de ejecución del binario.
  • staticJump(addr,string): Continua el descubrimiento mediante la instrucción del segmento de código de un salto condicional obtenido en el anterior método simplemente desensamblando la función donde se sitúa dicha instrucción.
  • dbgCalls(addr): Usamos el debugger y obtiene el valor de todos los registros de todas las llamadas a funciones dada una dirección de memoria de interés particular.
  • dbgRegisters(addr,register): Usamos el debugger y obtiene el valor de un registro en particular en todas las coincidencias de ese registro en las instrucciones. Por tanto, obtendrá el valor de ese registro en diferentes partes del código.

Como hemos visto finalmente con los dos últimos métodos, el Debugger nos facilita bastante la vida por ello en dbgCalls (viendo el video 0x03) nos da información de la dirección de memoria donde esta el valor del buffer introducido correspondiente al User. Partiendo de ahí se me ocurre buscar coincidencias de esa dirección de memoria, donde es usada, en que función, si es comparada antes de un salto condicional etc… Es cuestión de crear, ser creativo y hacer crecer nuestro conocimiento realizando reversing de muchos binarios, así que would do you like to follow me? 😉

Un saludo.

 

Un comentario en «Scripting con IDAPython – Creamos, creativos y crecemos»

Los comentarios están cerrados.