IDA análisis de marco de pila y desmontaje

Marco de la pila

No mas detalles.

1. Distribución de variables locales.

La primera tarea del compilador es calcular el espacio requerido para las variables locales de la función . La segunda tarea del compilador es determinar si estas variables pueden asignarse en el registro de la CPU, o si deben asignarse en la pila del programa. En cuanto al método de asignación específico, no tiene nada que ver con el llamador de la función o la función llamada. Vale la pena señalar que al examinar el código fuente de una función, generalmente es imposible determinar el diseño de la variable local de la función.

Inserte la descripción de la imagen aquí

Funciones que usan punteros de pila para referirse a variables de marco de pila

Se calcula que las variables locales requieren al menos 76 bytes de espacio de pila (3 enteros de 4 bytes y 1 búfer de 64 bytes). Como se muestra en la Figura 6-3, es una implementación de marco de pila para llamar a demo_stackframe, suponiendo que no utiliza el registro de puntero de marco (por lo tanto, el puntero de pila ESP como el puntero de marco). Al ingresar demo_ stackframe, puede usar la siguiente línea de "Preámbulo" para configurar este marco de pila:
Inserte la descripción de la imagen aquí

  • El eip guardado en la imagen de arriba es la dirección de retorno

La generación de una función que usa el puntero de la pila para calcular todas las referencias de variables requiere que el compilador haga más trabajo. Debido a que el puntero de la pila cambia con frecuencia, el compilador debe asegurarse de que siempre use el desplazamiento correcto al hacer referencia a cualquier variable en el marco de la pila .
Tome el código de llamada para bar en la función demo_stackframe como ejemplo: de
Inserte la descripción de la imagen aquí
acuerdo con el desplazamiento en la Figura 6-3, el empuje en ➊ empuja con precisión la variable local y en la pila. A primera vista, parece que el impulso en references hace referencia incorrecta a la variable local y nuevamente. Sin embargo, debido a que estamos tratando con un marco basado en ESP y el empuje en ➊ modifica el ESP, cada vez que el ESP cambia, todos los desplazamientos en la Figura 6-3 se ajustarán temporalmente. Por lo tanto, después de ➊, el nuevo desplazamiento de la variable local z referenciada correctamente en la inserción en ❷ se convierte en [esp + 4]. Al analizar las funciones que utilizan punteros de pila para referirse a las variables del marco de la pila, debe tener cuidado, prestar atención a cualquier cambio en el puntero de la pila y ajustar todas las compensaciones de variables futuras en consecuencia. La ventaja de usar un puntero de pila para referirse a todas las variables de marco de pila es que todos los demás registros aún pueden usarse para otros fines.
Después de completar demo_ stackframe, debe volver a la persona que llama. Finalmente, debe usar la instrucción ret para mostrar la dirección de retorno requerida desde la parte superior de la pila e insertarla en el registro del puntero de instrucciones (en este caso, EIP). Antes de abrir la dirección de retorno, la variable local debe eliminarse del elemento de la pila, de modo que cuando se ejecuta la instrucción ret, el puntero de la pila apunta correctamente a la dirección de retorno guardada. El "fin" de esta función especial es el siguiente: debido a que un registro se usa específicamente como puntero de marco, y el puntero de marco se configura en el punto de entrada de la función a través de un fragmento de código, el trabajo de calcular el desplazamiento de la variable local se vuelve más fácil. En los programas x86, el registro EBP generalmente se usa exclusivamente como puntero de trama. Por defecto, la mayoría de los compiladores generan código para usar el puntero de trama, ignorando la opción que especifica que se debe usar el puntero de pila. Por ejemplo, GNU gcc / g ++ proporciona la opción del compilador -fomit-frame-pointer para generar funciones que no dependen de registros de puntero de cuadro fijo.
Inserte la descripción de la imagen aquí
一下讲的为重点:

Funciones que usan punteros de marco para referirse a variables de marco de pila:

Para entender el uso de dedicada puntero de marco a la StackFrame marco de pila estructura demo_, que por debajo de este código "preámbulo" como ejemplo:
Inserte la descripción de la imagen aquí
el número anterior ④ ⑤ ③
( sobre el programa de 32 bits: )
Empuje ❸ instrucción de la persona que llama se utiliza para guardar la corriente El valor de EBP.
Las funciones que siguen la interfaz binaria de la aplicación System V para procesadores Intel de 32 bits pueden modificar los
registros EAX, ECX y EDX , pero los valores de la persona que llama deben reservarse para todos los demás registros. Por lo tanto, si desea utilizar EBP como puntero de cuadro, antes de modificarlo, debe guardar el valor actual de EBP y restaurar el valor de EBP de la persona que llama al regresar a la persona que llama . Si necesita guardar otros registros para el llamante (como ESI o EDI), el compilador puede guardar estos registros mientras guarda el EBP, o posponer la operación de guardar hasta que se hayan asignado las variables locales. Por lo tanto, no hay una ubicación estándar para almacenar registros guardados en el marco de la pila.
Después de guardar el EBP, puede modificarlo para que apunte a la posición superior actual en la pila. Esto se realiza mediante la instrucción mov en ④, que copia el valor actual del puntero de la pila en el EBP. Finalmente, como en los marcos de pila no basados ​​en EBP, el espacio para las variables locales se asigna a ❺. El diseño del marco de la pila resultante se muestra en la Figura 6-4.
Inserte la descripción de la imagen aquí

  • El eip guardado en la imagen de arriba es la dirección de retorno

使用一个专用的帧指针,所有变量相对于帧指针寄存器的偏移量都可以很容易地计算出来(ebp不像esp那样变化)。许多时候,正偏移量用于访问函数参数,而负偏移量则用于访问局部变量。 Usando un puntero de marco dedicado, podemos cambiar libremente el puntero de la pila sin afectar el desplazamiento de otras variables en el marco. Ahora, la llamada a la barra de funciones se puede ejecutar de la siguiente manera: el
Inserte la descripción de la imagen aquí
número anterior es ⑥
después de ejecutar la instrucción de inserción en ⑥, el puntero de la pila ha cambiado, pero esto no afectará la instrucción de inserción posterior para acceder a la variable local z. Finalmente, una vez que la función completa su operación, el uso del puntero de cuadro requiere un código de "epílogo" ligeramente diferente porque el puntero de cuadro del autor de la llamada debe restaurarse antes de regresar. Antes de recuperar el valor inicial del puntero de trama, las variables locales deben borrarse de la pila. Sin embargo, dado que el puntero de cuadro actual apunta al puntero de cuadro original, esta tarea se puede realizar fácilmente. En un programa x86 que utiliza EBP como puntero de trama, el siguiente código es un código típico de "epílogo": Inserte la descripción de la imagen aquí
debido a que esta operación es muy común, la arquitectura x86 proporciona una instrucción de licencia para realizar fácilmente esta tarea.
Inserte la descripción de la imagen aquí
Los registros y las instrucciones utilizados por otras arquitecturas de procesador ciertamente serán diferentes, pero el proceso básico de construir marcos de pila no es significativamente diferente. Independientemente de la arquitectura, debe estar familiarizado con el típico código de "prólogo" y "epílogo" para comenzar a analizar rápidamente el código en la función que le interesa más.

2. Vista de pila IDA

Obviamente, el marco de la pila es un concepto de tiempo de ejecución. Sin la pila y el programa en ejecución, el marco de la pila no puede existir. Dicho esto, esto no significa que pueda ignorar el concepto de marcos de pila cuando utilice herramientas como IDA para el análisis estático. El archivo binario contiene todo el código necesario para configurar el marco de la pila de cada función. Al analizar cuidadosamente este código, podemos entender la estructura del marco de la pila de cualquier función, incluso si esta función no se está ejecutando.
De hecho, algunos de los análisis más complicados en IDA es determinar específicamente el diseño del marco de la pila de cada función desmontada por IDA. Durante el análisis inicial, IDA recordará cada operación push o pop, así como cualquier otra operación aritmética que pueda cambiar el puntero de la pila, como sumar o restar constantes, y hacer todo lo posible para monitorear el puntero de la pila durante la ejecución de la función. Comportamiento El primer objetivo de este análisis es determinar el tamaño específico del área variable local asignada al marco de la pila de funciones. Otros objetivos incluyen: determinar si una función usa un puntero de cuadro dedicado (por ejemplo, identificando secuencias push ebp / mov ebp.esp) e identificando todas las referencias de memoria a variables dentro del cuadro de la pila de funciones. Por ejemplo, si IDA encuentra la siguiente instrucción en el cuerpo de demo_stackframe: mírela
Inserte la descripción de la imagen aquí
para saber que el primer parámetro de la función (en este caso, a) se carga en el registro EAX (consulte la Figura 6-4).

Al analizar cuidadosamente la estructura del marco de pila, la AIF puede distinguir parámetros de acceso de función (que se encuentra por debajo de la dirección de retorno) se guarda referencias a memoria y las variables locales (ubicados por encima de la dirección de retorno guardados) referencias. IDA también tomará medidas adicionales para determinar qué ubicaciones de memoria dentro del marco de la pila están referenciadas directamente. Por ejemplo, aunque el tamaño del marco de la pila en la Figura 6-4 es de 96 bytes, solo veremos 7 variables (4 variables locales y 3 parámetros) a las que se hace referencia.

La comprensión del comportamiento de una función generalmente se reduce a conocer el tipo de datos que la función manipula. Al leer la lista de códigos de desmontaje, ver el marco de la pila de la función es su primera oportunidad para comprender los datos manipulados por la función. IDA proporciona dos vistas para cualquier marco de pila de funciones: 1. Vista de resumen y 2. Vista detallada. Para entender estas dos vistas, tomamos como ejemplo la siguiente función demo_ stack frame compilada con gcc:
Inserte la descripción de la imagen aquí
en este ejemplo, proporcionamos los valores iniciales c y b para las variables x e y, respectivamente, y el valor inicial constante 10 para la variable z . Además, el primer carácter del búfer de matriz local de 64 bytes se inicializa con la letra 'A'. El código de desmontaje IDA correspondiente a esta función es el siguiente: los
Inserte la descripción de la imagen aquí
números anteriores están ordenados de arriba a abajo de la siguiente manera: ①④②⑤⑥⑦⑦⑧⑨③③
A continuación presentamos una gran cantidad de contenido en el código anterior para familiarizarse gradualmente con el código de desmontaje IDA.

重要:
Primero, a partir de ①, basado en el análisis del código de "preámbulo" de la función, IDA cree que esta función usa el registro EBP como un puntero de pila.

Desde la posición ②, gcc asigna 120 bytes (78h equivale a 120) de espacio variable local en el marco de la pila, que incluye 8 bytes para pasar dos parámetros a la barra en ❸; sin embargo, aún es mucho más grande Los 76 bytes que estimamos anteriormente indican que el compilador a veces llena el espacio variable local con bytes adicionales para garantizar una alineación especial dentro del marco de la pila.

A partir de ④, IDA proporciona una vista de resumen de la pila, que enumera cada variable / parámetro directamente referenciado en el marco de la pila, así como el tamaño de la variable / parámetro y su distancia de desplazamiento desde el puntero del marco. IDA nombrará la variable en función de su posición en relación con la dirección de retorno que se guarda. Las variables locales se encuentran por encima de la dirección de retorno guardada, mientras que los parámetros de la función se encuentran por debajo de la dirección de retorno guardada. El nombre de la variable local tiene el prefijo var_, seguido de un sufijo hexadecimal que indica la distancia (en bytes) entre la variable y el puntero de cuadro guardado. En este ejemplo, la variable local var_ C es una variable de 4 bytes (palabra clave), que se encuentra por encima del puntero de trama guardado a una distancia de 12 bytes ([ebp-0Ch]).
Los nombres de los parámetros de función tienen el prefijo arg_, seguido de un sufijo hexadecimal que indica la distancia relativa desde el parámetro superior. Por lo tanto, el parámetro superior de 4 bytes se llama arg_0, y los parámetros siguientes son arg_4, arg_8, arg_C, etc. En este caso particular, arg_0 no aparece en la lista porque la función no utiliza el parámetro a. Como IDA no puede determinar ninguna referencia de memoria a [ebp + 8] (la ubicación del primer parámetro), arg_0 no aparece en la vista de resumen de la pila. Un vistazo rápido a la vista de pila de resumen revela que muchas ubicaciones de pila no tienen nombre porque no se pueden encontrar referencias directas a estas ubicaciones en el código del programa.
Inserte la descripción de la imagen aquí
Una diferencia importante entre la lista de códigos de desmontaje IDA y el análisis de marco de pila que realizamos anteriormente es que no se puede encontrar una referencia de memoria similar a [ebp-12] en la lista de códigos de desmontaje. En cambio, IDA ha reemplazado todos los desplazamientos constantes con los nombres de los símbolos correspondientes a los símbolos en la vista de la pila, y sus desplazamientos relativos desde el puntero del marco.Esto se hace para garantizar que IDA genere un código de desmontaje más avanzado. Es más fácil tratar con nombres simbólicos que tratar con constantes numéricas. De hecho, con el fin de facilitarnos recordar los nombres de las variables de la pila, IDA permite modificar los nombres de cualquier variable de la pila de manera arbitraria, lo que se introducirá más adelante. La vista de pila de resumen es un "mapa" de los nombres generados por IDA a sus correspondientes compensaciones de marco de pila. 例如,在反汇编代码清单中出现内存引用[ebp+arg_8]的地方,可以使用[ebp+10h]或[ebp+16]代替(即基址+偏移/位移地址). Al ver esto, de repente fui brillante.

Si prefiere ver el desplazamiento numérico , IDA se lo mostrará con gusto. Haga clic con el botón derecho en arg_8 en ⑤, y aparecerá el menú contextual que se muestra en la Figura 6-5, que proporciona varias opciones para cambiar el formato de visualización.
Inserte la descripción de la imagen aquí
En este caso particular, dado que el código fuente se puede comparar, podemos usar una serie de pistas en la ventana de desensamblaje para unir los nombres de variables generados por IDA con los nombres usados ​​en el código fuente.
(1) Primero, demo_stackframe usa 3 parámetros: a, by c. Corresponden a las variables arg_0, arg_4 y arg_8 (aunque la lista de códigos de desmontaje ignora arg_0 porque no se hace referencia a ella).
(2) La variable local x se inicializa con el parámetro c. Por lo tanto, var_C corresponde a x porque x es inicializado por arg_8 en ⑥.
(3) De manera similar, la variable local y se inicializa mediante el parámetro b. Por lo tanto, var_5C corresponde a y porque y es inicializado por arg_4 en ❼.
(4) La variable local z corresponde a var_60 porque se inicializa con el valor 10 en ❽.
(5) El búfer de matriz de caracteres de 64 bytes comienza en var_58 porque el búfer [0] es
inicializado por A (ASCII 0x41) en ❾ .
(Puede definirse primero.)
(6) Las dos variables que llaman barra se transfieren a la pila en ③ en lugar de presionar la pila. Esta es una práctica típica de la versión actual (3.4 y posterior) de gcc. IDA reconoce esta convención y elige no crear referencias de variables locales para los dos elementos en la parte superior del marco de la pila. Además de la vista de resumen de la pila, IDA también proporciona una vista detallada del marco de la pila, que muestra cada byte asignado a un marco de la pila . Haga doble clic en cualquier nombre de variable relacionado con un marco de pila dado para ingresar a la vista detallada. En la lista anterior, haga doble clic en var_C para abrir la vista del marco de la pila que se muestra en la Figura 6-6 (presione ESC para cerrar la ventana).Inserte la descripción de la imagen aquí
** Dado que la vista detallada muestra cada byte en el marco de la pila, ocupará mucho más espacio que la vista de resumen (enumerando solo las variables referenciadas). ** La parte del marco de la pila que se muestra en la Figura 6-6 abarca 32 bytes, pero solo ocupa una pequeña parte de todo el marco de la pila. Tenga en cuenta que la función solo asigna nombres a los bytes directamente referenciados. Por ejemplo, el parámetro a correspondiente a arg_0 nunca se referencia en demo_stackframe. Como no hay referencias de memoria para analizar, IDA decidió no procesar los bytes correspondientes en la pila, y sus desplazamientos oscilaron entre +00000008 y + 0000000B. Por otro lado, en la lista de códigos de desensamblaje, arg_4 se referencia directamente en ⑦, y su contenido se carga en el registro EAX de 32 bits. Basado en el hecho de que se transfirieron datos de 32 bits, IDA concluyó que arg_4 es una variable de 4 bytes y lo marca como tal (db define un byte de almacenamiento, dw define dos bytes de almacenamiento, también llamados palabras; dd Definir 4 bytes de almacenamiento, también llamado palabra doble).

图6-6中显示的两个特殊值分别为s和r (前面均带有空格)。这些伪变量是IDA表示被保存的返回地址(r)和被保存的寄存器值(s,在本例中,s仅代表EBP)的特殊方法。 Dado que cada byte en el marco de la pila debe mostrarse, estos valores también se incluyen en la vista del marco de la pila para mayor integridad.

La vista del marco de la pila es útil para analizar en profundidad el mecanismo de trabajo interno del compilador. En la Figura 6-6, está claro que el compilador insertó 8 bytes adicionales entre los punteros de trama guardados y la variable local x (var_C). En el marco de la pila, el desplazamiento de estos bytes es de -00000001 a -00000008. Además, después de realizar varias operaciones aritméticas en los desplazamientos relacionados con cada variable enumerada en la vista de resumen, puede encontrar que el compilador asignó 76 bytes (no 64 en el código fuente) al búfer de caracteres en var_58 Bytes). Si es un desarrollador de compiladores, o está dispuesto a analizar el código fuente de gcc, de lo contrario, solo puede especular sobre la razón por la cual el compilador asigna estos bytes adicionales de esta manera. En la mayoría de los casos, puede atribuir la asignación de estos bytes adicionales al relleno realizado por la alineación, y estos bytes generalmente no afectan el comportamiento del programa. Después de todo, si el programador solicita 64 bytes y obtiene 76 bytes, el programa no debe mostrar un comportamiento diferente, especialmente si los bytes utilizados por el programador no exceden los 64 bytes solicitados. Por otro lado, si usted es un desarrollador de programas crack y sabe que puede desbordar este búfer en particular, entonces debe darse cuenta de que debe proporcionar al menos 76 bytes (en lo que respecta al compilador, este es un búfer Tamaño efectivo), de lo contrario, lo que desea ver no sucederá.

———————————————————————————————————————————————————————— ——————————— El
contenido anterior se refiere a la “Guía autorizada IDA pro”

8 artículos originales publicados · Me gusta4 · Visitas 290

Supongo que te gusta

Origin blog.csdn.net/qq_45521281/article/details/105416279
Recomendado
Clasificación