ESTUDIO COLECTIVO DE DESPROTECCIONES
Última Actualizacion: 25/10/2001

Programa La Memoria en W32: intro W95 / W98 / NT
Descripción Suministrar conocimiento que permita comprender cómo W32 traduce direcciones virtuales en direcciones físicas.
Tipo  
Tipo de Tutorial [X]Original, []Adaptación, []Aplicación, []Traducción
Url  
Protección  
Dificultad 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas SoftIce v3.24
Objetivo Suministrar conocimiento que permita comprender cómo W32 traduce direcciones virtuales en direcciones físicas.
Cracker nuMIT_or
Grupo KuT
Fecha 15 de Octubre de 1999

INTRODUCCION

La gestión de memoria en W32 se realiza a través de tres mecanismos:

 

1- memoria virtual: cuando se quiere gestionar grandes estructuras de objetos.
2- proyección de archivos en memoria: para gestionar grandes paquetes de datos en los archivos y compartir datos entre procesos distintos.
3- montículos (heaps): para gestionar muchos bloques pequeños de datos.

El tema está suficientemente desarrollado en tres artículos clásicos de Randy Katz:

 

1- Managing Virtual Memory in Win32
2- Managing Memory-Mapped Files in Win32
3- Managing Heap Memory in Win32

En este artículo me limitaré a explicar un poco la gestión de la memoria virtual en W32, concetrándome en la manera cómo W32 traduce direcciones virtuales a direcciones reales. Se trata de una adpatación de la sección "Finalmente, 32 bits" del capítulo "Gestión de memoria y entrada/salida de archivos" del libro Programación en Windows 95 de Charles Petzold.

 

 

AL ATAQUE

¿QUÉ ES MEMORIA VIRTUAL?
==========================
A medida que los programas de aplicación se han hecho más sofisticados y más grandes, se ha necesitado de ampliar las posibilidades de la memoria dinámica de los sistemas. Debido a lo poco económico que resulta atender la gran exigencia de memoria simplemente implementando ships con elevada capacidad de almacenamiento, este problema se ha abordado vía software, a través de sistemas operativos que ofrecen gestión de memoria virtual.

Es un mecanismo de gestión de la memoria de un sistema que implementa un espacio de memoria ficticio, llamado espacio de direcciones o de nombres, el cual es mucho mayor que el espacio de memoria real física que posee el hardware del sistema. De esta manera, el programador al desarrollar aplicaciones para un SO con memoria virtual, no tiene que ocuparse de la memoria física, simplemente programa como si tuviera a su disposición una gran espacio de direcciones para cada proceso.

 

¿CÓMO ES POSIBLE LA GESTIÓN DE MEMORIA VIRTUAL?
=================================================
El programador desarrolla sobre un gran espacio de memoria virtual, compila o ensambla sus fuentes para producir los ejecutables de sus aplicaciones. Es todo lo que hace. El SO se encarga de lo demás: traduce las direcciones virtuales en direcciones físicas y carga en la RAM del sistema sólo aquellos bloques de la aplicación que necesi ta, nunca carga todo el ejecutable ya que éste seguramente ocuparía todo el espacio de memoria física disponible y el sistema colapsaría.



¿CÓMO TRADUCE W32 DIRECCIONES VIRTUALES A REALES?
=====================================================
W32 requiere la presencia de un microprocesador Intel 386, 486 o Pentium. Estos procesadores usan direccionamiento de memoria de 32 bits y por tanto son capaces de acceder 2 elevado a la 32, es decir, 4.294.967.296 bytes (4 gigabytes o GB) de memoria física. Por supuesto, la mayoría de los usuarios de W32 no están cerca de ese límite.

Las direcciones de 32 bits usadas por los programas W32 para acceder al código y los datos no son las direcciones físicas de 32 bits que el microprocesador usa para direccionar la memoria física. La dirección que utiliza la aplicación se llama dirección «virtual» la cual es una dirección ficticia que se traduce a una dirección física a través de una «tabla de página».

Para leer una instrucción o dato en la RAM, el CPU necesita colocar en el BUS de direcciones esa dirección donde debe estar ubicada la dirección o dato solicitado. Por ejemplo, si quiere acceder a la memoria de video, que está en 0A0000h, el CPU debe colocar este valor en el BUS.

Como las direciones de las aplicaciones son direcciones virtuales, el CPU debe traducirlas a direcciones físicas antes de acceder a los datos en la RAM.

Las aplicaciones suelen ignorar este proceso. El programa parece almacenarse en un espacio de direcciones de 32 bits y no hay nada extraño cuando se tiene que acceder a esta memoria. Sin embargo, es conveniente hacerse una idea de lo que significa esto.

W32 pagina la memoria física, la divide en «páginas» con una longitud de 4096 bytes (4 KB). Una máquina equipada con 8 megabytes de memoria tiene 2048 páginas. W95/98 mantiene una colección de tablas de página (ellas mismas páginas 4 KB) para traducir direcciones virtuales a direcciones físicas.

Cualquier proceso en W32 tiene su propia «página de directorio», que es una colección de hasta 1024 entradas de 32 bits almacenadas contiguamente, una después de otra. La dirección física de la página de directorio activa se almacena en un registro del microprocesador llamado CR3 (CONTROL REGISTER 3), que se cambia cuando el SO conmuta el control entre procesos. Los 10 bits altos de una dirección virtual especifican una de las 1024 entradas posibles en esta página de directorio. Los 20 bits altos de la entrada de la página de directorio indica una dirección física de una tabla de página (los 12 bits inferiores de la dirección física se definen a cero). Esto referencia otra página, que también tiene hasta 1024 entradas de 32 bits. Los 10 bits del medio de la dirección virtual referencian una de estas entradas. De nuevo, la entrada tiene una dirección física de 20 bits para indicar la posición de comienzo de un marco de página, que es una dirección física. Los 12 bits inferiores de una dirección virtual apuntan a una posición física dentro de este marco de página.

Mostrado simbólicamente, uno puede representar una dirección virtual de 32 bits (que es con lo que trabaja una aplicación) como una entrada de página de directorio de 10 bits (d), una entrada de tabla de página de 10 bits (p) y un offset de 12 bits:

 

dddd-dddd-ddpp-pppp-pppp-oooo-oooo-oooo

Para cada proceso, el microprocesador almacena un valor de 20 bits en el registro CR3 (r de registro):

 

rrrr-rrrr-rrrr-rrrr-rrrr

La dirección física de comienzo de la página de directorio activa del proceso es:

 

rrrr-rrrr-rrrr-rrrr-rrrr- 0000-0000-0000

Recuérdese que todas las páginas se almacenan en límites de 4 KB, por tanto, cada página comienza en una dirección con los 12 bits inferiores igual a cero. El microprocesador primero accede a la dirección física:

 

rrrr-rrrr-rrrr-rrrr-rrrr-dddd-dddd-dd00

Esta posición contiene otro valor de 20 bits (t de tabla):

 

tttt-tttt-tttt-tttt-tttt

lo cual indica la dirección física de comienzo de una tabla de página:

 

tttt-tttt-tttt-tttt-tttt-0000-0000-0000

El microprocesador accede luego a una dirección física:

 

tttt-tttt-tttt-tttt-tttt-pppp-pppp-pp00

Almacenado en este área hay un valor de 20 bits de un marco de página (c de marco):

 

CCCC-CCCC-CCCC-CCCC-CCCC

La dirección fisíca final de 32 bits es una combinación de este marco de página con los 12 bits de offset inferiores de la dirección virtual:

 

CCCC-CCCC-CCCC-CCCC-CCCC-0000-0000-0000

Esta es la dirección física. Es todo.

Puede parecer que traducir una dirección virtual en dirección física es un proceso que lleva mucho tiempo, pero realmente no es así. Los microprocesadores Intel 386, 486 y Pentium tienen una memoria cache interna que puede guardar estas tablas de página dentro del microprocesador. La traducción se realiza de forma muy rápida sin ninguna penalización de tiempo. Esta doble paginación (páginas que se guardan en una página) ofrece a cada aplicación un límite teórico de aproximadamente un millón de páginas de 4 KB.

 

EMPLEANDO SICE
================
Podemos revisar el proceso de traducción de memoria virtual a memoria física con SICE.

Carguemos notepad.exe con el loader de SICE. Cuando se despliega la pantalla de SICE, el depurador está apuntando a la primera instrucción del programa cuya dirección virtual es 00401000h. A partir de esta dirección podemos calcular la dirección física donde se ubica esta instrucción en la RAM.

Traduzcamos el valor hexadecimal de la dirección virtual a binario:

 

0000 0000 0100 0000 0001 0000 0000 0000

Tenemos:
1. Entrada del directorio de páginas o primario, diez bytes altos:

 

0000 0000 01 = 1

Es la entrada 1 del directorio primario

2. Entrada de la tabla de páinas o directorio secundario, diez bytes medios:

 

00 0000 0001 = 1

Es la entrada 1 del directorio secundario

3. Desplazamiento en el marco de página de memoria física, doce bytes bajos restantes:

 

0000 0000 0000 = 0

Comienzo del marco de página o desplazamiento cero. Recuérdese que es la primera instrucción del programa.

Para determinar exactamente la dirección física de la dirección debemos:

1. obtener la dirección física donde está el directorio de páginas activas o directorio primario. Este valor está en CR3 y lo obtenemos con el comando 'CPU' de SICE. Cada entrada de este directorio es un puntero al directorio secundario.

2. obtener la dirección de la entrada en el directorio primario donde se encuentra la dirección del comienzo del directorio secundario. Sabemos la dirección del inicio del directorio primario por los 20 bits altos del contenido de CR3. La entrada dentro del directorio primario que dice donde está la tabla que interesa está indicada por los diez bits altos de la dirección virtual. Como cada entrada del directorio primario es de 32 bits = 4 bytes, hay que multiplicar el número de entrada por 4 para obtener la dirección de la entrada correspondiente en el directorio primario.

3. obtener la dirección física dónde se encuentra la tabla de páginas o directorio secundario. Con la dirección de la entrada del directorio de página donde está la dirección de la tabla de páginas activas, se puede emplear el comando PEEK de SICE para obtener esta última dirección:

 

PEEK D DIRECCIÓN_DE_ENTRADA_DE_DIRECTORIO_PRIMARIO

4. obtener la dirección de la entrada en la tabla de páginas donde está la dirección del inicio o marco de la página donde está la instrucción apuntada por la dirección virtual. Sabemos la dirección del inicio del directorio secundario por los 20 bits altos del contenido la entrada del directorio primario. La entrada dentro del directorio primario que dice donde está el mmraco de página que buscamos está indicada por los diez bits medios de la dirección virtual. Como cada entrada del directorio secundario es de 32 bits=4 bytes, hay que multiplicar el número de entrada por 4 para obtener la dirección de la entrada correspondiente en el directorio secundario.

5. obtener la dirección física dónde se encuentra el marco de página donde se ubica la instrucción señalada por la dirección virtual. Con la dirección de la entrada del directorio secundario donde está la dirección del marco de páginas, se puede emplear el comando PEEK de SICE para obtener esta última dirección, ya que este comando devuelve el contenido de una dirección física:

 

PEEK D DIRECCIÓN_DE_ENTRADA_DE_DIRECTORIO_SECUNDARIO

6. obtener la dirección física de la instrucción en la página específica. Este valor se obtiene a partir de los 20 bits altos del contenido en la entrada correspondiente en el directorio secundario y los 12 bits bajos en la dirección virtual. Estos bits bajos de la dirección virtual son un desplazamiento dentro del marco de página donde se ubicó el contenido de la aplicación.

7. obtener el contenido en la dirección física señalada por el desplazamiento dentro del marco de página donde se encuentra el código o los datos indicados por la dirección virtual. Finalmente el comando

 

PEEK D DESPLAZAMIENTO_EN_MARCO_DE_PÁGINA

devuelve, invertida, la instrucción en octal en la dirección virtual. Para comprobar esto, si el puntero de SICE apunta a esta dirección virtual el comando

 

D EIP

devuelve el mismo valor en la ventana de datos de SICE. También podemos verificar esto revisando el código en octal de la instrucción correspondiente. Debemos ver lo mismo pero invertido. El comando

 

CODE ON

lo mostrará.

Continuemos con notepad.exe.

Ya tenemos los valores que corresponden a la dirección virtual de la primera instrucción de notepad.exe

La dirección física de la página de directorio o directorio primario lo obtenemos con el comando CPU de SICE: 008170000h. Traduzcámoslo a binario:

 

0000 0000 1000 0001 0111 0000 0000 0000

Los primeros 20 bits de este número indican la dirección física del comienzo del directorio primario del proceso activo. Los diez bits altos de la dirección virtual nos dicen que se trata de la primera entrada en este directorio

 

0000 0000 1000 0001 0111 - 0000 0000 01 - 00
                    ^                                    ^
     bytes altos en CR3             -     bytes altos
                                                    en dir virtual

obtenemos una nueva dirección física, la de la entrada 1 de la tabla de pá-
ginas o directorio secundario: 00817004h.

Ejecutemos el comando PEEK D 817004.

 

PEEK D 00817004

SICE nos dá: 01EFE267

Obtenemos ahora la dirección física del directorio secundario. En binario:

 

0000 0001 1110 1111 1110 0010 0110 0111

Los doce bits altos de este valor apuntan al inicio del directorio secundario. Los diez bits medios indican una entrada dentro de este directorio.

 

1100 0001 1011 1011 1111 - 00 0000 0001 - 00
                    ^                                    ^
 bytes altos en 01EFE267      -      bytes medios
                                                   en dir virtual

En hexadecimal: 01EFF004H

Esta entrada contiene la dirección del marco de página en memoria física donde se encuentra instrucción indicada por la dirección virtual.

Ahora obtengamos la dirección del marco de página:

 

PEEK D 01EFF004

Obtenemos un valor como 01DEC225h. Traduzcamos este valor a binario:

0000 0001 1101 1110 1100 0010 0010 1001

Los 20 bits altos de este valor son la dirección física de un marco de página, es decir, del comienzo de una página de memoria física. Si agregamos a este valor el valor indicado por los doce bits bajos de la dirección virtual obtenemos la dirección física donde se haya la instrucción señalada por la dirección virtual:

 

0000 0001 1101 1110 1100 - 0000 0000 0000
                    ^                                    ^
bytes altos en 01DEC225h     -     bytes bajos
                                                    en dir virtual

PEEK D 01DEC0000

SICE devuelve 83EC8B55

El valor devuelto por este comando corresponde a la instrucción en octal, pero invertido, a la que apunta la dirección virtual. Si esta es la dirección señalada por el puntero de SICE el siguiente comando nos devolverá el mismo valor regeresado antes:

 

D EIP

Vemos ahora que la ventana de datos de SICE apunta a 83EC8B55, en la dirección EIP, el mismo valor devuelto con el comando 'PEEK D 01DEC0000'

Hagamos "code on" y veamos la correspondencia entre el valor desplegado por el último PEEK y el código en octal desplegado por SICE, que seguramente será el mismo pero invertido

 

55         push  ebp
8BCE   mov   ebp, esp
83...


¿QUÉ VENTAJAS TIENE LA PAGINACIÓN?
====================================
Las ventajas de la paginación son:

Primero, las aplicaciones se aislan unas de otras. Ningún proceso puede inadvertidamente (o maliciosamente) escribir encima del espacio de código o datos de otro proceso, pues ni siquiera es capaz de direccionar la memoria del otro proceso sin el valor CR3 adecuado, y definir este valor es una tarea que sólo puede hacer el núcleo W32.

Segundo, este mecanismo de paginación resuelve uno de los problemas más básicos en un entorno multitarea: la consolidación de la memoria libre. En un esquema de direccionamiento más simple, a medida que se ejecutan múltiples programas y se sale de ellos, la memoria se va fragmentando. Si la memoria está demasiado fragmentada, los programas no se pueden ejecutar porque no tienen suficiente memoria contigua, incluso aunque la cantidad total de memoria libre sea la adecuada. Con la paginación, no es necesario consolidar la memoria física libre porque las páginas no tienen que ser contiguas. Cualquier cosa se gestiona manipulando las tablas de página. La única pérdida viene realmente por el espacio perdido en las propias tablas de página y la granularidad de las páginas de 4 KB.

Tercero, hay bits extra en las entradas de tabla de página de 32 bits además de las direcciones de 20 bits. Un bit indica que se ha accedido a una página particular (se denomina bit de «acceso»); otro que la página ha sido escrita (el bit «sucio»). W32 puede usar estos bits para determinar si una página de memoria se puede intercambiar (swapping) a un archivo de disco para obtener más memoria física libre. Otro bit («presencia») indica si la página se ha intercambiado al disco y se tiene que recargar en memoria.

Otro bit («lectura/escritura») indica si la página se puede escribir. Este bit protege el código de los punteros errantes. Por ejemplo, si se incluye la instrucción en código C siguiente en un programa Windows:

* (int *) WinMain 0;

se obtendrá un cuadro de mensaje diciendo: «Este programa ha realizado una operación ilegal y se cerrará». Este bit no evita que un programa compile el código fuente del programa y almacene las instrucciones de lenguaje ensamblador en memoria para ser ejecutadas.

Las direcciones virtuales tienen un ancho de 32 bits. El código y los datos (estáticos, de pila o localizados) de un programa tendrán direcciones entre 0x00000000 y 0x7FFFFFFF. El propio W32 usa direcciones desde 0x80000000 a 0xFFFFFFFF y aquí es donde se encontrarán los puntos de entrada a las librerías de enlace dinámico (DLL) de W32.

La cantidad total de memoria libre está determinada por la cantidad de memoria física libre y el área libre de disco duro disponible para intercambio de páginas. Como es normal con la gestión de memoria virtual, W325 emplea un algoritmo LRU (Least-Recently Used: usado-menos-reciente
mente) para determinar qué páginas intercambiar a disco. Los bits de «acceso» y «sucio» le ayudan en esta tarea. Las páginas de código no tienen que guardarse en disco porque las páginas de código no son escribíbles, simplemente se puede recargar desde el archivo .EXE o la librería de enlace dinámico (DLL).

A veces, se verá que se accede al disco cuando se mueve el ratón desde el área cliente de un programa al área cliente de otro programa. ¿Por qué ocurre esto? Windows tiene que enviar mcnsajes de movimiento de ratón a la segunda aplicación. Si el código del programa para procesar este mensaje no está cargado actualmente en memoria, Windows tiene que recargarlo desde el archivo de disco. Si tiene varios programas Windows grandes cargados simultáneamente y no tiene mucha memoria, probablemente verá una actividad inusual en el disco duro a medida que se mueve de programa a programa, pues Windows está recargando páginas descargadas previamente.

A veces, los programas individuales se ralentizan (o se detienen completamente) cuando Windows está realizando el intercambio (conmutación) de páginas. Las páginas de código se pueden compartir entre aplicaciones.

Esto es particularmente útil para las librerías de enlace dinámico. Varios programas ejecután-dose a la vez pueden usar las mismas funciones Windows 95, sin que sea necesario que el mismo código se cargue en memoria varias veces. Sólo es necesario una copia del código.

Excepto la granularidad de las páginas de 4 KB, la memoria física no se puede fragmentar porque la desfragmentación implica únicamente manipular las tablas de página. Sin embargo, la memoria virtual de una aplicación se puede fragmentar si una aplicación localiza, relocaliza y libera varios bloques de memoria. El limite de 2 MB para el código y los datos de una aplicación normalmente es suficiente para evitar problemas. Es mucho más fácil que un programa se quede antes sin memoria física que llegue a encontrar un límite en la memoria virtual. Pero puede ocurrir, y si tiene un programa donde este problema es concebible, puede que desee considerar la memoria «movible».

 

¿EL PROGRAMADOR PUEDE GESTIONAR LA MEMORIA VIRTUAL?
========================================================
Sí, por supuesto. Para ello W32 proporciona varias funciones API:

Esta función se emplea para reservar y comprometer espacio de memoria virtual:

LPVOID VirtualAlloc (LPVOID lpAddress,DWORD cbSize,
                DWORD fdwAllocationType, DWORD fdwProtec);

Para liberar el espacio de memoria comprometido:

BOOL VirtualFree (LPVOID lpAddress,SWORD cbSize,DWORD fdwFreeType);


Bueno. Espero que este breve escrito sobre la gestión de memoria virtual en W32 pueda entenderse y sea de utilidad.

Cualquier error u observación, por favor notifíquenme:
nuMIT_or@iname.com


[ Entrada | Documentos Genéricos | WkT! Web Site ]
[ Todo el ECD | x Tipo de Protección | x Fecha de Publicación | x Orden Alfabético ]
(c) Whiskey Kon Tekila [WkT!] - The Original Spanish Reversers.
Si necesitas contactar con
nosotros , lee esto antes e infórmate de cómo puedes ayudarnos