2012-04-19 40 views
5

Hoy decidí descompilar un sencillo programa "Hello world" escrito en C++ visual, usando IDA Pro.Ingeniería inversa C++

Con mi conocimiento previo, estaba seguro de que no encontraría la llamada inmediata a printf en el punto de entrada del ejecutable, y tenía razón. Encontré una gran cantidad de código que no fue escrito por mí y agregado por el compilador durante el proceso de compilación.

Me gustaría obtener una mejor comprensión de qué código se agrega durante el proceso de compilación. ¿Qué hace? ¿Hay algún "truco" para encontrar rápidamente "principal" y omitir todos los códigos innecesarios generados por el desmontaje?

Lo mejor que pude encontrar fue en este post: http://www.codeproject.com/Articles/4210/C-Reverse-Disassembly, diciendo la orden de ejecución de un ejecutable compilado utilizando Visual C++ es el siguiente:

  1. CrtlStartUp

  2. principal

  3. CrtlCleanUp

¿Podría obtener una respuesta más detallada?

+5

Muy específico para compiladores y plataformas. Dudo que obtendrás la respuesta exacta que deseas. – Matt

+1

Recomiendo [esta publicación] (http://stackoverflow.com/a/9952374/176769) como una hoja de ruta para cualquier persona de ingeniería inversa-aspirante a gurú. – karlphillip

+1

No tengo experiencia en ingeniería inversa, pero ¿no podría simplemente establecer un punto de interrupción del depurador al inicio de main para obtener la dirección relativa? O, como alternativa, ¿busca principal en un volcado de objetos del ejecutable? – bjhend

Respuesta

4

Hay varias cosas que requiere el estándar de C++ que probablemente encontrará.

Lo más importante es que debe haber un código que maneje la construcción de cualquier estática en la unidad de traducción principal antes de que se llame a main, y una función que luego de las principales elimina su destrucción. Además, el estándar requiere una función atexit que le permite registrar funciones adicionales a las que se debe llamar después de los retornos principales.

Por lo menos, el código de inicio debe ser capaz de construir esta estructura de datos de las funciones que se invocarán en el retorno desde la principal. Esta es una estructura de datos dinámica porque el programa debe agregarla al tiempo de ejecución, y el orden de las llamadas es el opuesto al registro (por lo que normalmente desea una estructura de datos que facilite la incorporación al lugar desde donde camina).

Pero, además, el estándar requiere que las estáticas en otras unidades de traducción se creen antes de que se ejecute cualquier función en esa unidad de traducción. A menudo, los compiladores simplemente arreglan todo en el enlazador para que todos se llamen antes de main, pero eso no es necesario. Esos compiladores que hacen las cosas de manera diferente, luego deben proporcionar los pasos a las rutinas de inicialización en el otro código de la unidad de traducción vinculada que llamará a la primera llamada de función.

Esto es bastante trabajo si utiliza cualquier biblioteca estándar. Recuerde, std :: cout es un objeto estático (duración estática, enlace no estático - alerta de palabras confusamente sobrecargada). Entonces eso significa aumentar las comunicaciones a su consola, que tendrá todas las API necesarias para su plataforma. Hay muchos tales objetos en el estándar.

Y luego, puede haber elementos específicos para su plataforma y/o compilador que preparen el proceso de alguna manera útil, o analicen variables de entorno, o carguen bibliotecas dinámicas/compartidas "estándar", o cosas similares.

Normalmente, salir es simplemente caminar por esa lista y de alguna manera proporcionar el valor de retorno de main al entorno, ya que la mayoría de los sistemas operativos modernos limpian después de ellos, pero puede haber cosas específicas del sistema además de eso.

2

Los compiladores actuales crean ejecutables masivos, por lo que incluso si encuentra el punto de entrada, le llevará un tiempo entenderlo y acceder a la sección que realmente necesita.

En su caso con la aplicación hello world puede usar IDA encontrar el punto de entrada en el diálogo de lista de funciones (no recuerdo el nombre exacto). Pero de nuevo, no recomiendo este enfoque, a menos que la aplicación sea muy pequeña.

El enfoque que estoy usando yo llamo "hasta enfoque de arriba" (c)

me gustaría empezar a analizar el comportamiento actual de la aplicación sin que ello suponga ninguna herramienta. Es un paso muy importante que le ahorrará mucho tiempo, ya que sabrá qué está buscando y cuándo sucede. Luego, determine "puntos débiles" como cadenas, valores constantes que puede encontrar con herramientas de análisis estático (IDA).

El siguiente paso es desmontar la aplicación y buscar los "puntos débiles" (módulo de cadenas en AIF) y luego encontrar referencias a ellos de qué funciones utilizadas (se puede utilizar la vista de jerarquía de los gráficos en las nuevas versiones de la AIF)

Si todavía no puede ver cómo funciona o si este código es llamado desde muchos lugares, no sabe cuál es el que necesita. Puede comenzar con el análisis de tiempo de ejecución y usar depurador (softice? :)) como ollydbg. Esto le mostrará las cosas que no son visibles con el análisis estático, como las funciones virtuales/indicadores de funciones, por ejemplo: llame a EAX.

Luego, acaba de procesar paso a paso hasta que obtenga lo que necesita.

Cuestiones relacionadas