| 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