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

Programa [ SEH - AIRBAG PARA TU CÓDIGO ] W95 / W98 / NT
Descripción Structured Exception Handling (Manejo Estructurado de Excepciones)
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  
Objetivo Tolerancia a errores graves cometidos por la aplicación
Cracker Mr.Silver [WkT!]
Grupo http://www.whiskeykontekila.org
Fecha 9 de Septiembre de 2001

INTRODUCCION

Saludos, hacia tiempo que no escribia un tutorial asi que me he decidido a escribir sobre un tema el cual parece ser bastante desconocido por la mayoria de los newbies, Structured Exception Handling (Manejo Estructurado de Excepciones).

El uso de SEH suele resultar al principio algo confuso, pero explicado de la manera correcta, no resulta un tema complicado. Como ventajas de usar SEH en nuestras aplicaciones, encontramos la más importante de todas: robustez y gran tolerancia a errores graves cometidos por la aplicación. Pero veamos antes, cuando y como se produce una excepción y para que sirven exactamente.

¿Para que sirven?

Su uso más común es controlar fallos graves de la aplicación e intentar recuperar el programa del error, aunque a veces resulta imposible. La mayoria de
veces el uso de SEH sirve para tener un manejador de errores personalizado que ofrezca al programador la información necesaria para poder subsanar el error, esto es así ya que por defecto windows tiene un manejador de excepciones genérico para las aplicaciones que no usan uno propio y a veces la información que aporta suele ser bastante excasa, este manejador genérico es controlado por la API UnhandledExceptionFilter.

¿Cómo funciona SEH?

El funcionamiento de SEH es algo complejo aunque no es dificil de entender. Hay que tener en cuenta que un manejador de excepciones es especifico para cada thread de la aplicación. Un thread es un hilo o hebra del proceso principal, un proceso puede tener multiples threads ejecutandose a la vez y cada uno de estos threads puede tener su propio manejador de excepciones. Es importante recordar que al establecer un manejador propio en un thread el manejador anterior queda en la cadena de manejadores de excepción, pero este no será llamado si el nuevo manejador no lo indica explicitamente.
Es muy importante tener esto en mente cuando estamos trabajando con un proceso ajeno, del cual desconocemos su comportamiento respecto al uso de manejadores, ya que es posible modificar el proceso ajeno para que establezca un manejador de excepciones previamente inyectado como una dll o como un thread remoto, de esta manera podriamos controlar las excepciones que pudieran causarse en el proceso ajeno, aunque siempre existe la posibilidad que el proceso ajeno modifique el manejador y perdamos así el control, más adelante comentaré una técnica que nos permitirá controlar estos cambios.Veamos ahora con un gráfico el desarrollo de una excepción desde que se produce hasta que se controla.

Al producirse una excepción, el sistema recibe el control (suponiendo que no exista un depurador en modo kernel en ejecución SICE TRW), tras esto el sistema comprueba si el proceso esta siendo depurado (ojo, un depurador que corre por debajo del sistema, como WinDbg o OllyDbg) si es así el sistema envia la excepción al depurador, en caso de que el proceso este ejecutandose de manera normal, el sistema pasa el control al manejador de excepciones del thread donde se produjo la excepción. Si el depurador no manejara la excepción, se pasaria al manejador genérico del sistema. Normalmente el manejador genérico suele terminar la ejecución con ExitProcess().Una vez el manejador recibe la excepción debe realizar alguna de las siguientes operaciones:

- Controlar la excepción e intentar recuperar la ejecución. Realizariamos las operaciones necesarias y tras esto retornariamos EXCEPTION_CONTINUE_EXECUTION.

- Ejecutar el manejador por defecto del sistema (en caso de que el manejador no sepa como controlar la excepción). Para ello simplemente retornariamos
EXCEPTION_EXECUTE_HANDLER.

- Continuar la busqueda en la cadena de manejadores del thread en busca de alguno que controle la excepción, si no se encontrara ninguno, se ejecutaria el manejador por defecto. El manejador retornaria EXCEPTION_CONTINUE_SEARCH.

¿Cuando se produce una excepción?

Las excepciones se producen cuando el procesador realiza una operación no válida bajo el contexto del thread actual. Se pueden distinguir las siguientes
excepciones más importantes.

- Acceso a memoria no válida: Se producen cuando un thread intenta acceder en un modo no permitido a una posición de memoria a la cual no tiene acceso. Por ejemplo se puede producir este tipo de excepción si el thread intenta escribir a una posición de memoria de solo lectura.

- División entre 0: Se produce cuando se intenta dividir un numero entre 0.

- Instrucción no valida intento de ejecución de instrucción privilegiada: Se produce cuando el procesador intenta ejecutar una instrucción que no pertenece a su juego de instrucciones, es decir al encontrar un código de operación desconocido. Tambien se producen estas excepciones si el thread intenta ejecutar una instruccion privilegiada, es decir cuando el thread se esta ejecutando en un modo de privilegio de usuario (Ring 3) y se intenta ejecutar una instrucción del sistema (Ring 0).

- Al llegar a un punto de ruptura: Es decir cuando se ejecuta una INT 3, el sistema notifica este hecho como una excepción, normalmente son los depuradores los que establecen los puntos de ruptura, aunque nada nos impide hacerlo manualmente. Debido a esto se puede implementar un truco anti-debugging: se establece un SEH que controle esta excepción y se ejecuta una INT 3, si el manejador toma el control, el proceso no esta siendo depurado.

Hay mas ocasiones en las cuales se generan excepciones, para una lista completa recomiendo que hecheis un vistazo al API de Windows, concretamente a la estructura EXCEPTION_RECORD.

¿Cómo se establece un manejador SEH?

Dependiendo del lenguaje de programación utilizado el manejador SEH puede establecerse de distintas formas, en lenguaje C++ se suele utilizar la siguiente
sintáxis:

void main()

{
__try {
int a=0,b=1;
b=b/a;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
MessageBox(NULL,"Divide by 0 exception","ERROR",MB_ICONINFORMATION); }
};

El código anterior establece un manejador SEH para el código ejecutado entre los parentesis del try. Si se produce alguna excepción en ese código, el bloque de código perteneciente al catch se ejecutará como manejador SEH, cabe destacar que dependiendo del compilador el manejador puede establecerse previamente a este código. Dado que se divide b entre 0, se produce una excepción y el mensaje de error aparecerá.En ensamblador podemos simular estas instrucciones con el uso de las siguientes macros:

@TRY_BEGIN MACRO Handler

pushad
mov esi,offset Handler
push esi
push dword ptr fs:[0]
mov dword ptr fs:[0],esp

ENDM

@TRY_EXCEPT MACRO Handler
jmp NoException&Handler
Handler:
mov esp,[esp+8]
pop dword fs:[0]
add esp,4
popad
ENDM

@TRY_END MACRO Handler
jmp ExceptionHandled&Handler

NoException&Handler:
pop dword fs:[0]
add esp 32+4

ExceptionHandled&Handler:
ENDM

En nuestro código en ensamblador hariamos:

@TRY_BEGIN Nombre_Handler
; código a chequear excepciones
@TRY_EXCEPT Nombre_Handler
; código a ejecutar si se produce una excepción
@TRY_END Nombre_Handler
; flujo de ejecución normal

Este código accede al TIB o TEB (Thread Information Block), el TIB esta almacenado en fs:[0] y tiene la siguiente estructura:

// Esta estructura está parcialmente documentada en el include NTDDK.H
// del DDK de Win NT
typedef struct _TIB
{
_EXCEPTION_REGISTRATION_RECORD pvExcept; //00h Cabeza de la cadena de manejadores
PVOID pvStackUserTop; //04h cima de la pila del usuario
PVOID pvStackUserBase; //08h base de la pila del usuraio
WORD pW16TDB; //0Ch W16 Task DataBase
WORD pvThunksSS; //0Eh SS selector usado para pasar a 16 bits
DWORD SelmanList; //10h
PVOID pvArbitrary; //14h Disponible para el uso de la app
PTIB ptibSelf; //18h Dirección lineal del TIB = R3TCB + 10h
WORD TIBFlags; //1Ch
WORD Win16MutexCount; //1Eh
DWORD DebugContext; //20h
DWORD pCurrentPriority; //24h
DWORD pvQueue; //28h selector de la cola de mensajes
PVOID* pvTLSArray; //2Ch Array de almacenamiento local del Thread
} TIB, *PTIB;

En fs:[0] tenemos un puntero a una estrutura de tipo _EXCEPTION_REGISTRATION_RECORD (pvExcept), esta estructura contiene dos punteros más,el primero apunta a la estructura del siguiente manejador SEH establecido en el thread actual (*Next) y el segundo puntero es exactamente la dirección del manejador de excepciones actual (PVOID Handler).La estructura seria esta:

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD *Next;
PVOID Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

Tal y como vemos en las macros de ensamblador, el código establece el manejador empujando a la pila primeramente el puntero al nuevo handler (PVOID Handler) y
seguidamente el puntero del manejador actual (push dword ptr fs:[0]) por ultimo solo tiene que corregir el valor ExceptionList (mov dword ptr fs:[0],esp)
Como es lógico el puntero a la cadena SEH lo tenemos en esp ya que previamente habiamos empujado los datos necesarios en la pila. Los otras macros hacen lo
propio para restaurar el manejador anterior, dependiendo de si se tuvo que controlar o no la excepción. Este método es anidable, lo cual nos permite
un control mas exacto dependiendo del código a ejecutar.Como información adicional destacar que el TIB esta situado dentro del R3TCB (Ring3 Thread Control Block) concretamente en el offset 10h del R3TCB. Este bloque de información contiene datos bastante interesantes sobre el thread actual, veamos su definición (esto no es necesario para usar seh pero nunca esta de más saber cosas nuevas):

// Estrutura de Bloque de control del thread en Ring 3 (R3TCB)

typedef struct _THREAD_DATABASE
{
DWORD Type; //00h = 6
DWORD cReference; //04h
PPROCESS_DATABASE pProcess; //08h PDB goo
DWORD someEvent; //0Ch un objeto de evento (Para que se usa???)
_TIB TIB; //10h TIB (Thread Information Block)
PPROCESS_DATABASE pProcess2;//40h otra copia del proceso del thread?
DWORD Flags; //44h
DWORD TerminationStatus; //48h Valor de retorno de etExitCodeThread
WORD TIBSelector; //4Ch
WORD EmulatorSelector; //4Eh
DWORD cHandles; //50h
DWORD WaitNodeList; //54h
DWORD un4; //58h
DWORD Ring0Thread; //5Ch
PTDBX pTDBX; //60h
DWORD StackBase; //64h
DWORD TerminationStack; //68h
DWORD EmulatorData; //6Ch
DWORD GetLastErrorCode; //70h
DWORD DebuggerCB; //74h
DWORD DebuggerThread; //78h
PCONTEXT ThreadContext; //7Ch
DWORD Except16List; //80h
DWORD ThunkConnect; //84h
DWORD NegStackBase; //88h
DWORD CurrentSS; //8Ch
DWORD SSTable; //90h
DWORD ThunkSS16; //94h
DWORD TLSArray[64]; //98h
DWORD DeltaPriority; //198h
// La versión recortada termina mas o menos aquí
// El resto de campos seguramente solo existen en la versión de depuración

DWORD un5[7]; //19Ch
DWORD pCreateData16; //1B8h
DWORD APISuspendCount; //1BCh # de veces que SuspendThread es llamó
DWORD un6; //1C0h
DWORD WOWChain; //1C4h
WORD wSSBig; //1C8h
WORD un7; //1CAh
DWORD lp16SwitchRec; //1CCh
DWORD un8[2]; //1D0h
DWORD Mutex?[4]; //1D8h max 4 level
DWORD hMutex[4]; //1E8h max 4 level,hMutex of each level
DWORD un9; //1F8h
DWORD ripString; //1FCh
DWORD LastTlsSetValueEIP[64];
} THCB, THREAD_DATABASE, *PTHREAD_DATABASE;

Esta estructura es bastante compleja y los datos que contienen derivan en mas estructuras si cabe más comlejas aún, por lo que queda fuera del objetivo de
este documento su análisis. Tan solo decir que para acceder a esta estructura podemos hacerlo de dos maneras:

Ring3TCB = (WORD)FS:[18h] - 10h

Ring3TCB = GetLinearAddress(FS)-10h

Por último la manera mas frequente y quizás la menos compleja de todas para establecer un manejador SEH es usar la API SetUnhandledExceptionFilter,esta
función es como sigue:

LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter );

La función recibe un puntero al manejador que se desea establecer como manejador por defecto para el thread actual, si previamente existia algún manejador, la
función retornara la dirección de este, en caso contrario obtendremos un NULL como valor de retorno. Puede ser de utilidad guardar el handler anterior y en
nuestro manejador retornar el control a este en caso de que el nuestro no sepa o no quiera manejar una excepción concreta. Notese que este comportamiento no
es obligatorio, es por esto que aqnque se establezca un manejador global para el thread, siempre puede ser sustituido por otro el cual no tiene porque llamar al
manejador previo. Este tipo de manejador es global para el thread hasta que se define otro manejador con esta misma API o cuando se defina un manejador usando el metodo del try..catch.
El manejador que se pase a esta API debe retornar uno de los siguientes valores:

EXCEPTION_EXECUTE_HANDLER: Retorna de UnhandledExceptionFilter y ejecuta el manejador asociado. Normalmente suele terminar el proceso.

EXCEPTION_CONTINUE_EXECUTION: Retorna de UnhandledExceptionFilter y continua la ejecución del thread en el punto donde sucedio la excepción, suponiendo que no se altere el Eip desde el manejador de excepciones, si se alterara el Eip la ejecución continuaria desde el nuevo eip establecido por el manejador.

EXCEPTION_CONTINUE_SEARCH: Proceder con una ejecución normal de UnhandledExceptionFilter. Esto significa obedecer los flags de la API SetErrorMode, o invocar el cuadro de dialogo de error de aplicación.

Ahora que ya sabemos como se establece el manejador, vamos a hechar un vistazo a los parametros que recibe cuando se produce la excepción.

LONG My_SEH_Handler( STRUCT _EXCEPTION_POINTERS *ExceptionInfo );

Esta podria ser la defiinción para un supuesto manejador SEH, como parámetros recibe un puntero a una estructura de tipo _EXCEPTION_POINTERS y retorna un
LONG conteniendo uno de los valores de retorno comentados previamente.

Veamos que tenemos en la estructura:

typedef struct _EXCEPTION_POINTERS
{
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;


Esta estructura nos ofrece dos punteros más, el primero contiene la información relativa a la excepción que se produjo (ExceptionRecord) y el segundo contienes
el estado de los registros del procesador cuando sucedio la excepción (ContextRecord).Seguimos mirando las estructuras, veamos que información tenemos sobre la excepción:

typedef struct _EXCEPTION_RECORD
{
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

ExceptionCode: El código de excepción, quizás este sea el dato más interesante para nuestro manejador, ya que con el podrá averiguar que tipo de excepción a
tenido lugar y decidir si puede controlarlo o no.Alguno de los códigos de excepción más comunes són:

EXCEPTION_ACCESS_VIOLATION 0xc0000005 El thread intentó leer o escribir a una dirección virtual a la cual no tiene acceso apropiado.

EXCEPTION_BREAKPOINT 0x80000003 Se encontró un punto de ruptura en el thread.

EXCEPTION_SINGLE_STEP 0x80000004 Un sistema de depuración paso a paso indicó que una instrucción fue ejecutada.

EXCEPTION_INT_DIVIDE_BY_ZERO 0xc0000094 El thread intentó dividir un valor entero entre 0.

EXCEPTION_ILLEGAL_INSTRUCTION 0xc000001d El Thread intentó ejecutar una instrucción no válida.

EXCEPTION_PRIV_INSTRUCTION 0xc0000096 El Thread intentó ejecutar una instrucción privilegiada no permitida en el modo de ejecución actual de la aplicación.

ExceptionFlags: Especifica los flags de la excepción, Si es 0 indica que la excepción es continuable, EXCEPTION_NONCONTINUABLE (1) indica una excepción no continuable. Cualquier intento de continuación de ejecución despues de una excepción no continuable causará una excepción de tipo EXCEPTION_NONCONTINUABLE_EXCEPTION (0xc0000025).

ExceptionRecord: Apunta a una estrutura de tipo EXCEPTION_RECORD. Las estruturas de excepción pueden ser encadenadas para proveer información adicional cuando suceden excepciones anidadas.

ExceptionAddress: Especifica la dirección (EIP) donde tuvo lugar la excepción, notese que este dato tambien lo podemos obtener de la estrutura ContextRecord.

NumberParameters: Especifica el número de parámetros asociados a la excepción. Este es el numero de elementos definidos en el array ExceptionInformation.

ExceptionInformation: Un array de argumentos adicionales (4 bytes cada elemento) que describen la excepción. La API RaiseException puede especificar este array de elementos para la mayoria de excepciones estos argumentos no estan definidos, tan solo para EXCEPTION_ACCESS_VIOLATION, tenemos los siguientes argumentos en el array:El primer elemento contiene un flag indicando el tipo de operación que causó la violación de acceso, Si es 0, el thread intentó leer datos inaccesibles. Si es 1 el thread intento escribir datos inaccesibles.
El segundo elemento de array indica la dirección virtual de los datos inaccesibles.Hemos terminado con la estrutura ExceptionRecord, veamos ahora ContextRecord, la cual nos ofrece interesante información sobre el estado de la CPU en el momento de la excepción.

typedef struct _CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
} CONTEXT,*PCONTEXT,*LPCONTEXT;

Como podemos ver tenemos los valores de los registros de propósito general y tambien los selectores, si disponemos de una CPU Pentium o superior también tendremos los registros de depuración (Drx). Si desearamos modificar cualquiera de estos datos, podriamos hacerlo antes de de salir del manejador pero para ello tendriamos que modificar el valor de ContextFlags para que Windows actualizará los valores al reanudar la excepción.Los flags que podemos pasarle son los siguientes:

CONTEXT_CONTROL: Si se modificó alguno de los registros de control siguientes: EBP, EIP,CS,EFlags,ESP,SS.

CONTEXT_INTEGER: Si se modificó alguno de los siguentes registros: EDI, ESI, EBX, EDX, ECX, EAX

CONTEXT_SEGMENTS: Si se modificó alguno de los siguentes registros de segmento GS, FS, ES,DS

CONTEXT_FLOATING_POINT: Se modificó algún registro o dato de la FPU, estos datos se encuentran en la estrutura _FLOATING_SAVE_AREA, definida como:

typedef struct _FLOATING_SAVE_AREA {
DWORD ControlWord;
DWORD StatusWord;
DWORD TagWord;
DWORD ErrorOffset;
DWORD ErrorSelector;
DWORD DataOffset;
DWORD DataSelector;
BYTE RegisterArea[80];
DWORD Cr0NpxState;
} FLOATING_SAVE_AREA;


CONTEXT_DEBUG_REGISTERS: Si se modificó alguno de los siguientes registros de depuración: DR0, DR1, DR2, DR3, DR6, DR7. Como nota indicar que los registros DR4 y DR5 estan reservados por Intel, asi que no hay acceso posible a ellos.


CONTEXT_FULL: Este flag es una combinación de todos los anteriores, por lo tanto se usará en caso de que se modifiquen varios registros de distinto tipo.


Veamos ahora un ejemplo de manejador de excepciones que controla las violaciones de acceso sobre una variable y continua la ejecución normal del programa:

#include <WINDOWS.H>
#include <string.h>
#include <stdio.h>

#define strSize 30
DWORD OldAccess=0; // para guardar antiguo acceso
char string[strSize]; // declara una cadena de caracteres


// Manejador de excepciones
LONG MyHandler(LPEXCEPTION_POINTERS ExceptionInfo)
{
DWORD DummyAccess;
DWORD *Access;
DWORD *Addr;

// obtenemos puntero a la información de la excepción

PEXCEPTION_RECORD pExcept=ExceptionInfo->ExceptionRecord;

// si es una violación de acceso, quizás la podemos controlar
if (pExcept->ExceptionCode==EXCEPTION_ACCESS_VIOLATION)
{


// Obtiene la dirección que se quiso acceder

Addr=(DWORD *)pExcept->ExceptionInformation[1];
// y el tipo de acceso que se realizó
Access=(DWORD *)pExcept->ExceptionInformation[0];
// Muestra la info
printf("\nException at %08Xh",pExcept->ExceptionAddress);
printf("\n\tCode %08Xh",pExcept->ExceptionCode);
printf("\n\tAccess: %s",Access == 0 ? "Read" : "Write");
printf("\n\tAddr %08Xh",Addr);
printf("\n\tBPM at %08Xh",string);
printf("\n\tEAX: %08X ECX: %08X EDX: %08X EBX: %08X",

ExceptionInfo->ContextRecord->Eax,
ExceptionInfo->ContextRecord->Ecx,
ExceptionInfo->ContextRecord->Edx,
ExceptionInfo->ContextRecord->Ebx);
printf("\n\tESI: %08X EDI: %08X EBP: %08X ESP: %08X",
ExceptionInfo->ContextRecord->Esi,
ExceptionInfo->ContextRecord->Edi,
ExceptionInfo->ContextRecord->Ebp,
ExceptionInfo->ContextRecord->Esp);
printf("\n\tEIP: %08X CS: %04X SS: %04X FS: %04X ES: %04X DS: %04X GS: %04X",
ExceptionInfo->ContextRecord->Eip,
ExceptionInfo->ContextRecord->SegCs,
ExceptionInfo->ContextRecord->SegSs,
ExceptionInfo->ContextRecord->SegFs,
ExceptionInfo->ContextRecord->SegEs,
ExceptionInfo->ContextRecord->SegDs,
ExceptionInfo->ContextRecord->SegGs);
printf("\nFlags: %08Xh",ExceptionInfo->ContextRecord->EFlags);
printf("\nDR0: %08X DR1: %08X DR2: %08X DR3: ",
ExceptionInfo->ContextRecord->Dr0,
ExceptionInfo->ContextRecord->Dr1,
ExceptionInfo->ContextRecord->Dr2,
ExceptionInfo->ContextRecord->Dr3);
printf("\nDR6: %08X DR7: %08X",
ExceptionInfo->ContextRecord->Dr6,
ExceptionInfo->ContextRecord->Dr7);

// Desprotegemos la memoria. hay k tener en cuenta que al ser
// memoria del proceso, se generan accesos dentro de la página
// donde esta ubicada la cadena, esta página tambien contiene el
// código del programa, por lo que se hace necesario el uso del
// modificador EXECUTE, o restaurar los valores antiguos de la
// página (tamaño pagina 4Kb) para que no se produzcan
// excepciones de acceso de ejecución al código
// si la dirección accedida esta dentro del rango protegido


if ( ((DWORD)Addr>=(DWORD)string) && ((DWORD)Addr<=(DWORD)string+4096) )
{
printf("\n\tAddr is inside our protected space %08Xh-%08Xh",string,string+strSize);
// la desprotegemos
VirtualProtect(string,strSize,OldAccess,&DummyAccess);
}
// y continuamos la ejecución
return EXCEPTION_CONTINUE_SEARCH;


}
return EXCEPTION_CONTINUE_SEARCH;
}

void main( void )
{

LPTOP_LEVEL_EXCEPTION_FILTER OldHandler;

OldHandler=SetUnhandledExceptionFilter(MyHandler);

// Protegemos la pagina, podemos usar PAGE_NOACCESS (con lo cual queda protegido parte del código del programa)
if (!VirtualProtect(string,strSize,PAGE_NOACCESS,&OldAccess))
{
printf("\nCan not protect memory");
} else
{
printf("\nMemory protected, now accesing...");
// Aquí sucede la excepción de acceso
strcpy(string,"Here it is the exception!");
// Aquí ya no sucederá nada ya que el manejador habrá restaurado los accesos
printf("\nCadena -> %s",string);
}

printf("\nPrevious access %08Xh",OldAccess);

if (!OldHandler) printf("\nNo previous SEH established"); else SetUnhandledExceptionFilter(OldHandler);

}

El código realiza lo siguiente: Establece el manejador con SetUnhandledExceptionFilter, luego protege la memoria virtual donde está alojada la variable de cadena "string", para ello usa la API VirtualProtect, la cual permite cambiar los priviliegios de acceso a la memoria indicada. Veamos mejor que dice la guía del API sobre VirtualProtect:

BOOL VirtualProtect(
LPVOID lpAddress, // dirección de memoria
DWORD dwSize, // tamaño
DWORD flNewProtect, // acceso deseado
PDWORD lpflOldProtect // dirección de un DWORD donde guardar el anterior acceso
);

flNewProtect: Los modos de acceso más usuales que se pueden especificar son los siguientes:

PAGE_NOACCESS : Desactiva cualquier tipo de acceso, cualquier intento de leer, escribir o ejecutar algo en la región protegida generará una excepción de violación de acceso (EXCEPTION_ACCESS_VIOLATION).

PAGE_READONLY: Desactiva todos los accesos excepto la lectura, cualquier tipo otro de acceso generará una exccepción de violación de acceso (EXCEPTION_ACCESS_VIOLATION).

PAGE_WRITECOPY: Desactiva todos los accesos excepto la escritura cualquier otro tipo de acceso generará una excepción de violación de acceso (EXCEPTION_ACCESS_VIOLATION).

PAGE_READWRITE: Desactiva todos los accesos excepto la lectura/escritura cualquier otro tipo de acceso generará una excepción de violación de acceso (EXCEPTION_ACCESS_VIOLATION).

PAGE_EXECUTE: Desactiva todos los accesos excepto la ejecución cualquier otro tipo de acceso generará una excepción de violación de acceso (EXCEPTION_ACCESS_VIOLATION). Este modo es utilizado normalmente para proteger areas de código del programa, evitando cualquier modificación no autorizada del código del programa.

PAGE_EXECUTE_READ: Combinación de PAGE_READONLY y PAGE_EXECUTE.

PAGE_EXECUTE_READWRITE: Combinación de PAGE_READONLY,PAGE_EXECUTE y PAGE_WRITECOPY. Este modo implica un acceso total a la memoria.

lpflOldProtect: Un puntero a un DWORD donde se guardará el modo de acceso anterior establecido sobre la página. Es importante que este valor no sea NULL ya que en Win Nt/2k siempre debe de especificarse si no la llamada a VirtualProtect fallará. En este ejemplo se ha utilizado VirtualProtect ya que estamos tratando con memoria del proceso actual, en caso que tengamos que cambiar los accesos de un proceso ajeno usaremos VirtualProtectEx, esta API permite especificar el proceso mediante su Handle.

Una vez protegida la memoria se intenta copiar una cadena mediante strcpy, al haber protegido la memória se generará una excepción que será enviada a nuestro manejador (MyHandler). El manejador comprueba si la excepción fue provocada por una violación de acceso, si no es así se retorna del manejador con EXCEPTION_CONTINUE_SEARCH. Si la excepción es de acceso se procede a obtener la dirección de memória que se intentó acceder y el tipo de acceso que se quiso realizar, esta información la saca del array ExceptionInformation. Seguidamente muestra el estado de la CPU cuando se produjo la excepción para ello extrae los datos necesarios de la estructura ContextRecord. Por ultimo y para tener un control preciso comprueba que el acceso se realizó exactamente sobre uno de los
caracteres de la cadena. Porqué? Simplemente porque como dice el API, VirtualProtect siempre protegerá como mínimo una página, esto es un inconveniente ya que se protegen bytes que no deberian, por lo que el código del ejemplo comprueba que efectivamente es uno de los carácteres de la cadena. Por último se restaura el estado de protección que tenia la memoria (OldAccess). Nótese que el código anterior deberia de restaurar el modo de acceso anterior aunque la excepción fuera provocada por un acceso fuera de la cadena pero dentro de la página, el código no realiza esto ya que es meramente un ejemplo con una excepción controlada. Otro detalle a destacar es que nuestro manejador no retorna el control al manejador anterior, para realizar esto podriamos sustituir el código final del manejador:

return EXCEPTION_CONTINUE_SEARCH;

por

if (OldHandler)
{
OldHandler(ExceptionInfo);
}

De esta manera llamariamos al antiguo manejador pasandole los datos de la excepción. Nótese tambien que OldHandler deberia declararse como global.

[ SEH - Y LAS EXCEPCIONES DE DEPURACIÓN]
[ 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