[ SEH - 
  AIRBAG PARA TU CÓDIGO ]
  Por  Mr. 
  Silver
Mr. 
  Silver 
  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?
 
  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?
 
   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?
 
   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?
 
   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 __except 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]
  O cómo 
  implementar puntos de ruptura por hardware
  Por  Mr. 
  Silver
Mr. 
  Silver  
  Mediante el uso de la técnica descrita anteriormente podriamos crear 
  un sistema de puntos de ruptura sobre memória aunque ciertamente los 
  inconvenientes de VirtualProtect hacen esta 
  tarea algo engorrosa. Veamos ahora otra alternativa para implementar puntos 
  de ruptura usando un manejador SEH. Hemos visto que tenemos una estructura con 
  el contexto actual del thread, en su interior se encuentran los registros de 
  depuración (solo a partir de Pentium o superior), podemos usar estos 
  registros para establecer un punto de ruptura por hardware sobre cualquier dirección 
  donde se ejecute una instrucción o donde se lea/escriba memória, 
  esto es debido a que los registros de depuración son capazes de provocar 
  excepciones (0x80000004L) 
   de depuración 
  que pueden ser controladas por un manejador SEH. Para ello necesitamos saber 
  como funcionan los registros de depuración. A partir del procesador 386 
  los PC's tienen 6 registros de depuración, DR0, DR1, DR2, DR3, DR6, DR7 
  los registros DR4 y DR5 estan reservados por Intel y no se utilizan.Los cuatro 
  primeros registros contienen 4 posibles direcciones de memoria donde se establece 
  el punto de ruptura, el registro DR7 (Control Debug Register) controla el tipo 
  de punto de ruptura para cada uno de esos puntos de ruptura (DR0 a DR3) y el 
  registro DR6 ( Debug Status Register) controla el tipo de punto de ruptura que 
  se activó por ultima vez. Veamos El formato de los registros de depuración: 
  
Extraido del documento: Intel Architecture Software Developer’s Manual Volume 3: System Programming
DR7: El registro de control de depuración (DR7) activa o desactiva puntos de ruptura y establece las condiciones que deberán cumplirse para que tenga lugar la excepción. El significado de cada bit para este registro es como sigue:

L0 hasta L3 (activar punto de ruptura local) flags (bits 0, 2, 4, and 6) Activa (si estan establecidos) la condición de ruptura para el punto de ruptura asociado para la tarea actual. Cuando se detecta una condición y su flag Ln está activo, se genera una excepción de depuración. El procesador automáticamente borra estos flags en cada cambio de tarea para evitar rupturas no deseadas en las nuevas tareas.
G0 hasta G3 (activar punto de ruptura global) flags (bits 1, 3, 5, and 7) Activa (si estan establecidos) la condición de ruptura para el punto de ruptura asociado para todas las tareas. Cuando una condición de ruptura es detectada y su flag Gn asociado está activo, se genera una excepción de depuración.El procesador no borra estos flags al cambiar de tarea, permitiendo que el punto de ruptura se active en otras tareas.
LE y GE (activar punto de ruptura exacto local y global) flags (bits 8 y 9) (No soportado en la familia de procesadores P6.) Cuando estan activos, estos flags hacen que el procesador detecte la instrucción concreta que causó la condición de ruptura para datos. Para compatibilidad con anteriores y posteriores arquitecturas de procesador, Intel recomienda que se pongan estos flags a 1 si se requieren puntos de ruptura exactos.
GD (activar detección general) flag (bit 13) Activa (cuando está establecido) la protección de los registros de depuración, la cual causa una excepción de depuración que se genera antes de la instrucción MOV que accedió al registro de depuración. Cuando se detectan este tipo de condiciones, el flag BD en el registro de estado DR6 se establece antes de generar la excepción.El procesador borra el flag GD una vez se ha entrado en el manejador de excepciones, para permitirle a este el acceso a los registros de depuración.
R/W0 hasta R/W3 (lectura/escritura) campos (bits 16, 17, 20, 21, 24, 25, 28, y 29): Especifica la condición de ruptura para el correspondiente punto de ruptura. El flag DE (debug extensions) en el registro de control CR4 determina como son interpretados los bits en los campos R/Wn.Cuando el flag DE esta activo, el procesador interpreta estos bits de la siguiente manera.
00—Parar sólo si se ejecutan instrucciones.
01—Parar solo en accesos de escritura a datos.
10—Parar en accesos de escritura o lectura de E/S.
11—Parar en accesos de lectura o escritura a datos, pero no en ejecución.
Cuando el flag DE esta borrado, el procesador interpreta los bits de R/Wn de la misma manera que para los procesadores Intel386™ y Intel486™, esto es de la siguiente manera:
00—Parar sólo si se ejecutan instrucciones.
01—Parar solo en accesos de escritura a datos.
10—Sin definir.
11—Parar en accesos de lectura o escritura a datos, pero no en ejecución.
LEN0 hasta LEN3 (Longitud) campos (bits 18, 19, 22, 23, 26, 27, 30, y 31): Especifica el tamaño de la posición de memoria especificada en la dirección del correspondiente registro (DR0 hasta DR3) Los bits son como sigue:
00—1-byte de longitud
01—2-bytes de longitud
10—Sin definir
11—4-bytes de longitud.
Si el campo correspondiente RWn en el registro DR7 es 00 (ejecución de instrucción), entonces el campo LENn deberia ser 0. El efecto de usar cualquier otra longitud es indefinido.
DR6: El registro de estado de depuración DR6, informa sobre las condiciones que fueron muestreadas cuando tuvo lugar la última excepción de depuración, este registro sólo se actualiza cuando se genera una excepción.
bits B0 hasta B3 (condición de ruptura detectada) Indican (cuando estan activos) que la condición de ruptura asociada se cumple cuando se generó la excepción de depuración. Estos flags se establecen si la condición descrita para cada uno de los puntos de ruptura por el campo LENn, y R/W del registro DR7 es cierta. Estos bits se establecen incluso si el punto de ruptura no se activó por los flags Ln y Gn del registro DR7.
BD (acceso a los registros de depuración) flag (bit 13): Indica que la siguiente instrucción accederá a uno de los registros de depuración (DR0 a DR7). Este flags se activa cuando el flag GD (general detect) en el registro DR7 esta activo.
BS (trazado paso a paso) flag (bit 14) Indica (cuando está activo) que la excepción de depuración fue activada por el modo de trazado paso a paso (activado mediante el flag TF en el registro EFLAGS). El trazado paso a paso es la excepción de depuración con mayor prioridad. Cuando el flag BS esta activo, cualquier otro bit del registro de estado puede ser establecido.
BT (cambio de tarea) flag (bit 15) Indica (cuando está activo) que la excepción de depuración resultó de un cambio de tarea donde el flag T (debug trap flag) en el TSS (Task-State Segment) de la tarea objetivo fue establecido.(Para más info sobre el TSS referirse a la sección 6.2.1. del Intel Architecture Software Developer’s Manual Volume 3: System Programming). No hay ningún flag en el registro DR7 que permita activar o desactivar esta excepción; el flag T del TSS es sólo un flag de activación.
Como podemos ver básicamente necesitamos el registro DR7 y los registros de direcciones DR0 a DR3, el uso del registro DR6 es opcional y solo se usa si desea tener un control mas preciso sobre los puntos de ruptura, en este tutorial sólo pretendo mostrar las capacidades básicas de los registros de depuración, para aquellos que deseen profundizar más les recomiendo que consulten el PDF Intel Architecture Software Developer’s Manual Volume 3: System Programming. Bien, como ya muchos sabrán el acceso directo a los registros de depuración es una tarea que solo esta permitida en Ring0, vamos a ver ahora un método para acceder a ellos desde Ring3 aunque no sea directamente el método nos resuelve la papeleta. Se basa en el uso de las APIs GetThreadContext y SetThreadContext:
BOOL SetThreadContext(
    HANDLE hThread, 	// handle
del thread 
    CONST CONTEXT *lpContext  	//
dirección donde se leera la nueva info del contexto
   ); 
BOOL GetThreadContext(
    HANDLE hThread, 	// handle
del thread 
    LPCONTEXT lpContext  	//
dirección donde almacenar la info del contexto
); 
Si estamos bajo NT,  el handle al thread deberá 
  tener privilegios de acceso de tipo THREAD_SET_CONTEXT.
  Alternativamente podemos usar GetCurrentThreadContext 
  o SetCurrentThreadContext si lo que pretendemos 
  es alterar el contexto del thread actual. Como podemos apreciar los contextos 
  son propios para cada thread, es decir cada thread tiene su contexto y Windows 
  se encarga de modificar este contexto cada vez que se cambia de thread (llamemoslo 
  tarea), por lo que un proceso puede tener multiples threads con un contexto 
  por cada thread.La clave del asunto está en los bits del registro DR7, 
  dependiendo de los bits que activemos podremos lograr un punto de ruptura que 
  tendrá lugar sobre la dirección indicada en el registro de depuración 
  DR0 a DR3.
Un ejemplo, viendo el significado de los bits de DR7 podemos deducir el valor adecuado de DR7 para poner un breakpoint sobre la dirección en el registro DR0:
Estas serian las mascaras correspondientes para cada uno de los registros (DR0 a DR3):
DR0_LOCAL_EXACT_BPM_ENABLED 
  equ 1b
  DR0_GLOBAL_EXACT_BPM_ENABLED equ 10b
DR0_W equ 
  010000000000000000b
  DR0_IO equ 100000000000000000b
  DR0_RW equ 110000000000000000b
  DR0_EXECUTION equ 0b
DR0_LEN1 
  equ 0b
  DR0_LEN2 equ 01000000000000000000b
  DR0_LEN4 equ 11000000000000000000b
  DR0_LENU equ 10000000000000000000b
DR1_LOCAL_EXACT_BPM_ENABLED 
  equ 100b
  DR1_GLOBAL_EXACT_BPM_ENABLED equ 1000b
DR1_W equ 
  0100000000000000000000b
  DR1_IO equ 1000000000000000000000b
  DR1_RW equ 1100000000000000000000b
  DR1_EXECUTION equ 0b
DR1_LEN1 
  equ 0b
  DR1_LEN2 equ 01000000000000000000000000b
  DR1_LEN4 equ 11000000000000000000000000b
  DR1_LENU equ 10000000000000000000000000b
DR2_LOCAL_EXACT_BPM_ENABLED 
  equ 10000b
  DR2_GLOBAL_EXACT_BPM_ENABLED equ 100000b
DR2_W equ 
  01000000000000000000000000b
  DR2_IO equ 10000000000000000000000000b
  DR2_RW equ 11000000000000000000000000b
  DR2_EXECUTION equ 0b
DR2_LEN1 
  equ 0b
  DR2_LEN2 equ 01000000000000000000000000000000b
  DR2_LEN4 equ 11000000000000000000000000000000b
  DR2_LENU equ 10000000000000000000000000000000b
DR3_LOCAL_EXACT_BPM_ENABLED 
  equ 1000000b
  DR3_GLOBAL_EXACT_BPM_ENABLED equ 10000000b
DR3_W equ 
  010000000000000000000000000000b
  DR3_IO equ 100000000000000000000000000000b
  DR3_RW equ 110000000000000000000000000000b
  DR3_EXECUTION equ 0b
DR3_LEN1 
  equ 0b
  DR3_LEN2 equ 01000000000000000000000000000000000000b
  DR3_LEN4 equ 11000000000000000000000000000000000000b
  DR3_LENU equ 10000000000000000000000000000000000000b
// Bits generales
LOCAL_EXACT_BPM_ENABLED 
  equ 100000000b
  GLOBAL_EXACT_BPM_ENABLED equ 1000000000b
  LOCAL_EXACT_BPM_DISABLED equ 000000000b
  GLOBAL_EXACT_BPM_DISABLED equ 0000000000b 
  GLOBAL_EXACT_BPM_ENABLED equ 1000000000b  
  
  GENERAL_DETECT_ENABLED 
  equ 10000000000000b
  RESERVED_BIT10 10000000000b 
  // el típico 0x400 que nos indica SICE ;)
Veamos ahora una rutina que podria servir para establecer un punto de ruptura de ejecución sobre DR0
// Pone 
  un BPM sobre el Thread especificado en la dirección especificada usando 
  el 
  // registro DR0 como contenedor de la dirección, en DR7 se setean los 
  bits para
  // activar el BPM sobre DR0
  void PutBPM(HANDLE hThread,DWORD 
  Address)
  {
// Estructura para establecer el contexto del thread
CONTEXT Regs;
// obtenemos contexto del thread
GetThreadContext(hThread,&Regs);
// activamos los flags para los registros de depuración, que es lo unico que tocaremos del contexto
Regs.ContextFlags=CONTEXT_DEBUG_REGISTERS;
// ponemos DR7 para k active un BPM en DR0
Regs.Dr7=LOCAL_EXACT_BPM_ENABLED|DR0_EXECUTION|DR0_LOCAL_EXACT_BPM_ENABLED|DR0_LEN1;
// sobre Address
Regs.Dr0=Address;
// y establecemos de nuevo el contexto del Thread
SetThreadContext(hThread,&Regs);
  }
La rutina es bastante sencilla y se podría modificar para que permitiera una mayor versatilidad, como elegir el DRx y el tipo de punto de ruptura, pero eso se deja como práctica para el lector :), antes de llamar a esta rutina se deberia establecer el manejador de excepciones con SetUnhandledExceptionFilter, el manejador podría ser algo así:
// Manejador 
  de excepciones para cuando el BPM surta efecto
  static LONG WINAPI MyBPMHandler(LPEXCEPTION_POINTERS 
  pExceptStruct)
  {
PEXCEPTION_RECORD ExcepRecord;
PCONTEXT Context;// cogemos puntero al contexto y al registro de la excepción
Context=pExceptStruct->ContextRecord;
ExcepRecord=pExceptStruct->ExceptionRecord;// Es una excepción de trazado paso a paso (0x80000004L)
if (ExcepRecord->ExceptionCode==STATUS_SINGLE_STEP)
{// puede k sea la nuestra
// aquí hacemos lo que nos plazca, como mostrar un mensaje o cualquier otra cosa
........................................................
........................................................
// borramos el BPM
Context->Dr0=0;
// el bit 10 del registro DR7 esta reservado, por lo que hay que dejarlo a 1
Context->Dr7=GLOBAL_EXACT_BPM_DISABLED|LOCAL_EXACT_BPM_DISABLED|RESERVED_BIT10;
// modificamos el contexto completo (es necesario reestablecer el contexto ya que si no la excepción entrara en un bucle infinito
Context->ContextFlags=CONTEXT_FULL|CONTEXT_DEBUG_REGISTERS;
// y seguimos
return EXCEPTION_CONTINUE_EXECUTION;}
  return EXCEPTION_CONTINUE_SEARCH;
  }
Este sería el esqueleto 
  básico del manejador, por supuesto que la cosa puede mejorarse para conseguir 
  algo más decente pero la base y el funcionamiento básico no cambiarian, aquí 
  teneis un proyecto de Visual C que muestra el uso de esta técnica poniendo 
  un punto de ruptura sobre el API MessageBox, 
  el manejador se encarga de mostrar la dirección del API y la dirección 
  de retorno donde se volverá tras ejecutar el MessageBox. 
  Con esto doy por terminado este tutorial aunque no descarto una nueva serie 
  con aspectos más avanzados.
aquí 
  teneis un proyecto de Visual C que muestra el uso de esta técnica poniendo 
  un punto de ruptura sobre el API MessageBox, 
  el manejador se encarga de mostrar la dirección del API y la dirección 
  de retorno donde se volverá tras ejecutar el MessageBox. 
  Con esto doy por terminado este tutorial aunque no descarto una nueva serie 
  con aspectos más avanzados. 
|  You are inside Reversed Minds pages. por  |