Bueno...esto lo tengo impreso desde hace tiempo y buscando el original por la red tan solo encontré una lección o dos...así que iré pasándolas a mano y publicándolas aquí en el foro...son pocas pero buenas...

¿ Qué es un Debugger ?

" Se podría definir como un sistema de software integrado en un sistema informático con el fin de identificar los errores lógicos de los programas (bugs) y proporcionar medios para enmendarlos. Tal software es utilizable al propio tiempo que se hacen funcionar los programas al objeto de que facilite información relativa a los procesos mientras estos están teniendo lugar. Un buen depurador (debugger) puede disponer de mandatos para mostrar el contenido de la memoria y de los registros,
e incluso para modificarlos, y para provocar la ejecución de rutinas durante la presentación de datos importantes que faciliten el diagnóstico de fallos. "

¿ Cómo podemos detectar los Debuggers ?

La detección de un debugger puede resultar complicada, aún así existen métodos muy sencillos que ofrecen buenos resultados.Algunos de estos métodos utilizan instrucciones privilegiadas y resulta complejo utilizarlos cuando nuestra aplicación se ejecuta bajo Windows en Ring3 ( bajo DOS no hay problema ), esto se debe a que Windows no nos permite utilizar las instrucciones privilegiadas necesarias para completar la detección, este tipo de detecciones solo las podemos realizar si estamos en Ring0 ( Máximo Privilegio ), lo cual implica la creación de un VXD o la utilización de algún truco que nos permita entrar en Ring0.

Hay que destacar también la proliferación de métodos que detectan exclusivamente a nuestro debugger preferido :SoftIce, uno de los más utilizados se denomina " MeltIce ", también es el más conocido y es el utilizado por los creadores del SoftIce para comprobar si este está en memória, podemos encontrarlo en la DLL nmtrans.dll dentro del directorio donde se encuentra el SoftIce.

Contrariamente a lo que puedas creer, la detección de un Debugger no ofrece en si ninguna protección, en realidad la protección se deriva después: ¿ Qué hacer una vez se ha detectado la presencia de un Debugger ?. Muchas aplicaciones simplemente informan al usuario mediante algún cuadro de diálogo ( esto nunca debe hacerse ) y después finalizan la aplicación. Otras aplicaciones són menos "consideradas" y se limitan a finalizar la aplicación sin mostrar
mensaje alguno ( este es un sistema aceptable ) y hay que son más agresivas y ponen en peligro la integridad de nuestra máquina realizando un cuelgue inmediato ( esto es muy peligroso y puede llegar a destruir información de otras aplicaciones) y las hay que se autodestruyen.

En mi opinión ninguno de los métodos anteriores resulta efectivo, frente a estos casos, la mayoría de crackers sospecharían lo que sucede y tomarían medidas inmediatas, tan solo dificultaríamos por momentos el trabajo del cracker. Sería más efectivo actuar de la siguiente manera:

" Continuar la ejecución del programa, el cracker no sospechará que le hemos detectado y deberá investigar si desea saber con certeza si le hemos detectado o no. Intentar volver loco al cracker, podemos alterar el funcionamiento de la aplicación para que el cracker no encuentre lo que busca, o incluso podemos enviar comandos al debugger que desactiven los breakpoints ( esto es posible si somos capaces de entrar en Ring0 ) y al finalizar la aplicación los volvemos a activar, esto confundirá a muchos crackers y se pasarán un buen rato pensando porque no funcionan sus breakpoints.

Veamos algunos ejemplos de como detectar la presencia de un debugger, en estos ejemplos veremos como detectar la presencia del SoftIce.

Ejemplo 1
/*
Función : IsSICELoaded

Descripción: Este método de detección es utilizado por la mayoría de compresores/encriptadores que se pueden encontrar en internet, se basa en la busqueda de la firma “ BCHK “ usando la INT3 que el SoftIce intercepta para sus servicios. Funciona tanto con MS-DOS como con Windows.

Retorna: TRUE si se detecta SoftIce
*/
__inline bool IsSICELoaded()
{
_asm {
push ebp
mov ebp,'BCHK' //'BCHK' ? 4243484Bh
mov eax,4 // Función 4h
int 3 // Llama a la interrupción 3
cmp al,3 // compara AL con 3
setnz al // si no es igual, SoftIce está presente
pop ebp
}
}

Este método es uno de los más sencillos de implementar, se basa en el uso de la INT 3 servicio 4h, cuando se le pasa el valor 'BCHK' (BoundsCheker) en EBP, este retornará un valor diferente a 3 en AL. Los crackers suelen defenderse de este método con un breakpoint de interrupción como el siguiente:

BPINT 3 if al==4

Este breakpoint nos permite modificar el valor oportuno y así evitar la detección del SoftIce.
Ejemplo 2
/*
Función : IsSICELoaded2

Descripción: Método de detección muy usado por los compresores/encriptadores, se basa en la INT 41, esta interrupción es utilizada por Windows para comprobar si hay un debugger instalado.
Solo funciona bajo Windows.

Retorna: TRUE si se detecta SoftIce
*/
__inline bool IsSICELoaded2()
{
_asm {
mov eax, 0x4F // AX = 004Fh
int 0x41 // INT 41 CPU – MS Windows debugging kernel – DEBUGGER
INSTALLATION CHECK
cmp ax,0xF386 // AX = F386h si hay un debugger presente
jz SoftICE_Detected
xor eax,eax
SoftICE_Detected:
}
}

Este método es también uno de los más sencillos de implementar, se basa en el uso de la INT 41 servicio 4Fh, esta retorna el valor F386h si hay un debugger presente. Los crackers suelen defenderse de este método con un breakpoint de interrupción como el siguiente:

BPINT 41 if al==4F

Este breakpoint nos permitirá modificar el valor oportuno y así evitar la detección de cualquier debugger que se este ejecutando.
Ejemplo3
/*
Función: IsSICELoaded3

Descripción: Método de detección muy usado por los compresores/encriptadores, similar al anterior pero usando la INT 68. Solo funciona bajo Windows.

Retorna: TRUE si se detecta SoftIce
*/
__inline bool IsSICELoaded3()
{
_asm {
mov ah,0x43
int 0x68
cmp ax,0xF386
jz SoftICE_Detected
xor eax,eax
SoftICE_Detected
}
}

Los crackers suelen defenderse de este método con un breakpoint de interrupción como el siguiente:

BPINT 68 if ah==43
Ejemplo 4
/*
Función: IsSICELoaded4

Descripción: Este método solamente funciona si la aplicación se está ejecutando en modo real ( MS-DOS ) , se basa en el uso de la interrupción 2Fh, utilizada por Windows para obtener un punto de entrada al dispositivo VXD identificado en el registro BX, el identificador del SoftIce es 0202h.
*/
__inline bool IsSICELoaded4()
{
_asm {
xor di,di
mov es,di
mov ax,1648h
mov bx,0202h // VxD ID of WinIce
int 2Fh
mov ax,es //ES:DI ? VxD API entry point
add ax,di
test ax,ax
jnz SoftICE_Detected
}
}

Los crackers se suelen defender de este método con un breakpoint de interrupción como el siguiente:

BPINT 2F if (ax==1684) && (bx=0202)
Ejemplo 5
*
Función: IsSoftIce9xLoaded

Descripción: Comprueba si el controlador VXD del SoftIce para Win 9x está cargado en memória, para ello utiliza la función CreateFile del API de Windows, esta función se encarga ( entre otras cosas ) de establecer comunicación con los dispositivos VXD.

Retorna: TRUE si SoftIce está en memoria.*/

__inline BOOL IsSoftIce9xLoaded()
{
HANDLE hFile;

// “\\.\SICE” sin las secuencias de escape
hFile = CreateFile ( “\\\\. \\SICE”,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

// Si se retorna un handle válido, SoftIce está en memoria

if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle(hFile); // Cierra la comunicación con el VXD
return TRUE; // y devuelve TRUE
}
return FALSE; // SoftIce no detectado
}
Ejemplo 6
/*
Función: IsSoftIceNTLoaded

Descripción: Comprueba si el controlador VXD del SoftIce para NT está cargado en memória,para ello utiliza la función CreateFile del API de Windows, esta función se encarga ( entre otras cosas ) de establecer comunicación con los dispositivos VXD.

Retorna: TRUE si SoftIce está en memoria.*/
__inline BOOL IsSof
tIceNTLoaded()
{
HANDLE hFile;

//// “\\.\NTICE” sin las secuencias de escape
hFile = CreateFile ( “\\\\. \\NTICE”,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle(hFile); // Cierra la comunicación con el VXD
return TRUE; // y devuelve TRUE
}
return FALSE; // SoftIce no detectado
}
Este ultimo método se denomina “ MeltIce “ , es de los más usados para detectar la presencia del SoftIce pero también de los más fáciles de engañar. Si te decides por este método NUNCA pases la cadena que hace referencia al VXD como en el ejemplo, ya que si lo haces así esta aparecerá dentro del ejecutable como una cadena legible,por lo que solo tendremos que buscar dentro del ejecutable la cadena “ SICE “ o “ NTICE “ con un editor hexadecimal y reemplazarla por cualquier otro valor como por ejemplo “ XICE “ , anulando por completo nuestro sistema de protección ( esto sucede la mayoría de los casos ).

Incluso si creamos la cadena dinámicamente, carácter a carácter, siempre podemos detectar este método con un breakpoint condicional sobre la función CreateFile

BPX CreateFileA if *(esp->4+4)=='SICE' || *(esp->4+4)=='SIWV' || *(esp->4+4)=='NTIC'

El funcionamiento de este breakpoint es sencillo, se comprueba que el primer parametro pasado a la función CreateFileA apunte a la cadena SICE o NTIC (se suma 4 para omitir la cadena ' \\.\ ' de caracteres de longitud ), si se cumple esta condición, el breakpoint se ejecuta (Notese el uso del condicional == y la operación lógica || , similar a la sintaxis del lenguaje C). Cuando el breakpoint surta efecto, solamente deberemos modificar la cadena pasada para que contenga cualquier otro nombre que no sea un VXD de Windows.

Se pueden usar métodos para detectar si hay un breakpoint sobre la función CreateFile, aunque estos métodos tamién pueden ser detectados. Lo cual deja a este sistema como unos de loa más inocentes ante un cracker. Esto es debido a que depende de la DLL kernel32 que contiene la función CreateFileA, y los sistemas de protección que dependen de DLL externas, son fáciles de detectar.

Estos son los métodos más básicos que existen para la detección del SoftIce o de cualquier otro debugger que se ejecute bajo Windows, existen otros pero son más complicados y serán comentados en otra lección.

Lección-1: Como detectar si hay breakpoints ( BPX)

Veamos un poco de teoría...

¿ Qué es un BreakPoint ?

BreakPoint o Punto de Ruptura es una de las grandes ventajas que todo debugger brinda al cracker, mediante un breakpoint, el cracker puede establecer un punto en el cual el debugger parará la ejecución del programa, posteriormente el debugger pasará el control del programa al cracker, con lo cual este podrá examinar que es lo que sucede al llegar al punto del código donde se estableció el breakpoint.

La mayor utilidad de los breakpoints es utilizarlos con funciones del API de Windows ( p- ej. MessageBoxA), esto nos permite tomar el control del programa antes de que se llame a la función del API sobre la que hayamos puesto un breakpoint. Es por eso que no es aconsejable informar al usuario de ningún error del sistema de protección mediante una función del API o de cualquier DLL externa.

¿ Cómo funciona un BreakPoint ?

Los BreakPoints ( usaré BPX para abreviar ) siempre han funcionado de la misma manera a lo largo de la larga vida de los debuggers, cuando el usuario decide establecer un BPX en alguna parte del código, este manda una instrucción al debugger ( en SoftIce : bpx dirección_código ) y el debugger reemplaza el primer byte del código por el código hexadecimal “ CC h “, este código equivale a la instrucción INT 3, la cual genera una interrupción ( la 3 en este caso). Cada vez que esta instrucción se ejecuta, se devuelve el control al debugger, este, reemplaza la INT 3 ( CC h ) por el byte de código que había previamente y muestra al usuario el código actual donde se encuentra el programa.

Ahora el usuario puede seguir trazando el programa si lo desea y examinar el código para cambiar el comportamiento del programa, pudiendo llegar a anular nuestro sistema de protección. Está claro que si pudieramos detectar si en nuestro código se han puesto BPX, podríamos abortar la ejecución del programa debido a una violación del sistema de seguridad ( siempre se debe hacer esto sin mostrar ningún mensaje de error ).

¿ Cómo podemos detectar los BreakPoints ?

Esto se puede conseguir fácilmente en ensamblador pero en C resulta algo más complicado, pero no por ello imposible. La teoría es la siguiente :

1) Obtener un puntero a la función o al inicio del código que queremos comprobar.
2) Leer el primer byte de código.
3) Comprobar si es igual a “ CC h “ ( INT 3 ) y actuar en consecuencia.

A continuación tienes un pequeño programa realizado en C que detecta si existe algún BPX sobre la función MessageBox del API de Windows. El código ha sido comprobado y funciona perfectamente compilandolo con Visual C++, no lo he probado con otros compiladores pero creo que funcionaría igual. Este código puede detectar cualquier debugger, ya que todos actuan igual, yo lo he probado con el SoftIce v4.0 para Windows 9x y funciona.

Para comprobar el funcionamiento de este código, compila el programa, pon un BPX sobre la función MessageBoxA ( bpx MessageBoxA si usas el SoftIce ) y ejecuta el programa, luego prueba desactivando el breakpoint.

Aquí está el código:

Código: Seleccionar todo

#include <stdio.h>
#include <windows.h>

#pragma warning ( disable : 4035 )   /*Desactiva la generación de warnings para las que 
                                      aparentemente no retornan nada */

__inline bool IsBPX(void * address)

{
   _asm {
      mov esi, address   // carga la dirección de la función
      mov al,[esi]       // comprueba si existe un breakpoint sobre la función
      cmp al, 0xCC       // esto se realiza comprobando que el primer byte de código
                         // de la función sea diferente de CC h, que es el código de operación
                         // de la INT 3 usada por TODO debugger
      je BPXed           // si encontramos CC h , la función tiiene puesto un breakpoint
                         // saltamos para devolver TRUE
      xor eax,eax        // FALSE
      jmp NOBPX          //  no hay breakpoint

      BPXed:

      mov eax, 1         // hay un breakpoint
      NOBPX:
  }
}
Las funciones están declaradas como funciones __inline , esto provoca que sean compiladas como si se tratara de macros y no funciones, es decir, cada vez que en nuestro código en C llamemos a una de estas funciones, el compilador no generará ninguna llamada sino que el código se añadirá cada vez que las llamemos. Esto evita que el cracker pueda parchear la función y con esto evitar que todas las llamadas para comprobar si hay un BPX sean inutilizadas con un solo cambio. Recomiendo hacer un uso intensivo de cualquiera de estas dos funciones para complicar la vida al cracker.

Esta segunda versión de la función, realiza lo mismo, pero lo hace de una manera menos evidente para que el cracker no pueda buscar posibles comparaciones de un registro con CC , en el desensamblado del código.

Código: Seleccionar todo


__inline bool IsBPX_v2(void * address)

{
   _asm {
         
      mov esi,address     // carga la dirección de la función
      mov al,[esi]        // comprueba si existe un breakpoint sobre la función
      mov ah,0x66         // carga ah con 66h ( mitad de CC h )
      add ah,ah           // lo suma a sí mismo dando 0xCC h, y se compara
      cmp ah,ah           // esto se realiza comprobando que el primer byte de código
                          // de la función sea diferente de CC h, que es el código de operación
                          // de la INT 3 usada por TODO debugger
      je BPXed
      xor eax,eax         // no hay breakpoint
      jmp NOBPX

      BPXed:

      mov eax, 1          // hay un breakpoint
      NOBPX:
  }
}

#pragma warning ( default : 4035 ) // establece la generación de warning para las funciones
                                   que no retornan valor, al valor por defecto del compilador
     void main()
 {
     void * addr ;
     addr = MessageBox ;

     if ( IsBPX_v2(addr))
  {
      MessageBox(NULL, “Estas intentando crackear este programa ?” , “Mensaje” , MB_OK | MB_ICONEXCLAMATION);

   }
      else MessageBox (NULL, “Bien chaval, parece legal...” , “Mensaje” , MB_OK);
}
Como se puede ver no es muy difícil de implementar y resulta bastante efectivo, no creo que el código necesite mucha explicación ya que las partes interesantes están comentadas, quizá la variable “addr” no sea necesaria pero así se ve más claro. Veamos los pros y los contras de este método.

Si el cracker llega a detectar el código que causa la detección del BPX, puede crear un pequeño programa o utilizar un editor hexadecimal que busque los códigos que pertenecen a las funciones de detección y que realize las modificaciones oportunas, evitándose así tener que ir de una en una, esto es debido a que el compilador siempre generará el mismo código las macros. Esto podríamos solucionarlo creando multiples versiones de las macros con instrucciones diferentes.

El método es difícil de detectar si se utiliza una macro bien camuflada, en este caso, la primera macro es muy sencilla de encontrar debido a la instrucción cmp al, 0xCC y probablemente sería poco efectiva ante un cracker experto.

Existe un método infalible para detectar la comprobación del breakpoint que muchos crackers ignoran ( ahora habrá menos jeje ), si en el SoftIce colocamos un BPM ( Breakpoint On Memory Access ) sobre la función que sospechamos, el debugger se detendrá justo después de leer el byte de código. En el ejemplo haríamos : BPM MessageBoxA R , esto le indica al SoftIce que detenga la ejecución si se intentan leer bytes de la función MessageBoxA.

Como se puede ver, cualquier truco antidebugging no está exento de su correspondiente truco anti-anti-debugging. Hasta la próxima lección.

Por Mr. Silver / WKT!

La información aquí vertida es exclusivamente para uso educacional, no puedo hacerme responsable del uso que se haga de esta, por lo que atiendo a la honradez de cada uno.

Apunte de Bloodday:
buen tutorial este de mr Silver, otra forma de detectar (no solo breackpoints sino cualquier cambio) es que en un hilo aparte, o en una dll en la funcion OnLoad hacer una comprobacion de integridad de cualquier parte de la memoria del ejecutable y compare con el resultado que deberia tener en caso de que no este alterado.
Lección-2: Detección de debuggers,la historia continua

Esta lección pretende añadir nuevos métodos de detección de debuggers, estos métodos los considero más extraños y algunos resultan difíciles de detectar. Pero para eso estamos aquí, para mostrar en que se basan y así poder mejorarlos y si alguno de ellos se interpone en nuestro camino, ya sabes...

MÉTODO 1:

Descripción:

Este método se suele utilizar muy poco, aunque es bastante efectivo ya que detectará el SoftIce aunque no esté en memoria, su funcionamiento es muy simple... comprueba la existencia en el registro de una de las siguientes claves, esto delatará la presencia de nuestro estimado amigo SoftIce.

1) HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\SoftIce

2) HKEY_LOCAL_MACHINE\Software\NuMega\SoftIce

Para comprobar la existencia de claves en el registro podemos usar la función del API RegOpenKey o RegOpenKeyEx.

Para detectar este método podemos utilizar un breakpoint en el SoftIce tal como:

BPX _regopenkey if*(esp->8+0x13)=='tICE' || *(esp->8+0x37)=='tICE'


MÉTODO 2:

Descripción:

Este método es muy efectivo ya que detecta cualquier debugger, aunque SOLO funciona bajo Win 9x, se basa en la comparación de las direcciones donde estan los manejadores de la INT 1 y la INT 3 , “icy” ( sin las comillas ) debe ser la dirección de un buffer de un mínimo de 6 bytes. El único método de detección de este método que conozco es por busqueda de los opcodes de la instrucción SIDT ( 0F 01 ) o del CMP, pero este puede tener combinaciones más diversas.

Personalmente desconocía este método hasta que me encontré con el en un programa de grabación de CD que usa este sistema, BlinWrite ( gracias a fanega por pasarmelo ). este sistema se puede explotar mucho más ya que de su funcionamiento deriva un fallo en el Windows que nos permitiría ejecutar código en Ring0 desde nuestra aplicación en Ring3.

El fallo reside en que el area de memória donde reside la IDT ( Interrupt Descriptor Table), no está protegida contra lectura/escritura, pero esto solo sucede en Win 9x y NO en NT. La IDT viene a ser algo así como una tabla de vectores ( similar a la antigua tabla del DOS ), donde se almacenan las direcciones de memória que apuntan al código que ejecuta al llamar a una interrupción. Veamos el código de ejemplo.

Código: Seleccionar todo

#include <windows.h>

#pragma pack(2)                    // alinea las estructuras a WORD

typedef struct_IDTGATE
{
     unsigned short gateOffsetLow;
     unsigned short gateSelector;
     unsigned short gateFlags;
     unsigned short gate OffsetHigh;
} IDTGATE;

typedef struct_IDT
{
     unsigned short idtLimit;
     IDTGATE* idtGate;
} IDT;

bool IsDebuggerRunning()
{
      IDT icy;
    _asm {
          SIDT [icy];
          mov eax, dword ptr [icy.idtGate]
          add eax,0x8
          mov ebx, dword ptr [eax]
          add eax, 0x10
          mov eax, dword ptr [eax]
          and eax, 0x0000FFFF
          nad ebx, 0x0000FFFF
          sub eax, ebx
          cmp eax, 0x1E
          je Sice
          xor eax,eax
Sice:
    }
}
MÉTODO 3:

Descripción:

Este método se basa en la busqueda de la cadena WINICE.BR en la memória, su funcionamiento es muy sencillo ya que es una simple búsqueda en memória. Resulta muy efectivo y bien implementado puede ser difícil de detectar, aunque sabiendo la dirección de memória que se analiza se puede utilizar un BPM ( Breakpoint On Memory Access ) para detectarlo. Doy las gracias a R!SC y a Stone por enseñarme este método.

Código: Seleccionar todo

Bool FindSoftIceString()

{
     _asm {
         mov al, 'W'
         mov edi, 0x10000
         mov ecx, 0x400000-0x10000
 More:
          repnz SCASB                 // 'W'
          jecxz  NotFound
          cmp dword ptr [edi], 'CINI'    // INIC
          jz  OK1
          jmp More
 OK1:
          add edi, 4
          cmp dword ptr [edi], 'RB.E'   //E.BR
          jnz More
          mov eax, 1
 NotFound:
   }
}
MÉTODO 4:

Descripción:

Este método consiste en ver si el registro de trazado DR7 contiene un valor distinto de 0, en caso afirmativo, el sistema esta bajo un debugger ( esto podemos comprobarlo si estando en SoftIce tecleamos el comando CPU ). Este método se basa en los registros de trazado ( Debug Registers Drx ) de los Pentium para su correcto funcionamiento, la aplicación que lo utilize debe tener privilegios de Ring0, esto implica programarnos nuestro VXD o hacer uso de algún tipo de truco que nos permita pasar de Ring3 a Ring0.

Existe un método que modificando la IDT nos permite redireccionar una interrupción a una dirección de código que este en nuestra aplicación, el código al ser ejecutado como una interrupción tendrá privilegios de Ring0, este método NO funciona bajo NT, destacar también que puede utilizarse otra interrupción que no sea la del ejemplo. Si por ejemplo usamos la INT 1, el SoftIce se irá a hacer puñetas, ya que el mismo usa esta interrupción. El siguiente código de ejemplo demuestra como podemos implementar esto desde C.

Código: Seleccionar todo

void Ring0Proc()

{
    _asm {
        mov   eax, dr7               // Instrucciones privilegiadas en Ring0
        iretd                              // esto se ejecutará como una interrupción por lo que deberemos
                                                 retornar tal y como lo hacen las interrupciones
    }
}

DWORD GetDebugReg7()

{
     _IDT   addr;                          // en el método 2 se puede ver la descripción de la estructura
        
       unsigned long lpOldGate    // esto es para guardar la dirección original

     _asm {
         sidt  fword ptr [addr]
         mov  ebx, dword ptr [addr+2]
         add  ebx, 8*5
          mov  dx,  word ptr [ebx+6]     // Salva el word alto de la puerta IDT
          shl  edx, 16d
          mov  dx, word ptr [ebx]          // word bajo
          mov  [lpOldGate], edx

          mov  eax, offset Ring0Proc    //  “instala nuestra rutina “ - que se ejecutará en Ring0
          mov  word ptr [ebx], ax          // word bajo
          shr   eax, 16d
          mov  word ptr [ebx+6], ax     // word alto

          int 5                                        // llama a la INT 5 que es la que hemos sustituido, esto ejecutará
                                                             nuestra rutina en Ring0
          cmp  eax, 0
          jne  Sice
          xor  eax, eax
  Sice:
          mov  ebx, dword ptr [addr+2]    // restaura la dirección original
          add   ebx, 8*5
          mov  edx, [lpOldGate]
          mov  word ptr [ebx], dx
          shr   edx, 16d
          mov  word ptr [ebx+6], dx
    }
}
MÉTODO 5:

Descripción:

Consiste en utilizar una función del VWIN32 que se encarga de despachar los servicios de la INT 41, a esta función se le pasan unos determinados valores que servirán para determinar la presencia del debugger. Solo funciona bajo Win 9x. Para poder detectar este método podemos utilizar uno de los siguientes breakpoints.

1) BPINT 41 if ax==4F

2) BPINT 30 if ax==0xF386

3) BPX Exec_PM_Int if eax==41 && edx->1C==4F && edx->10==002A002A

4) BPX Kernel32!ord_0001 if esp->4==002A002A && esp->8==4F

Código: Seleccionar todo

push 0000004F h       // función 4F h
push 002A002A h     // el word alto especifíca el VXD ( VWIN32 ), el word bajo especifíca el
                             servicio ( VWIN32_Int41Dispatch)                                                                                  
call Kernel32!ORD_001   //  VxD Call
cmp  ax, 0F386 h              // número mágico devuelto por los debugegrs
jz SoftICE_detected
MÉTODO 6:

Descripción:

Consiste en utilizar una llamada VXD que nos permite averiguar si hay instalado el controlador de dispositivo virtual que le indiquemos, para ello podemos utilizar dos de los identificadores de los que SoftIce dispone que son ( 202 h o 7A5F h ). Este método solo funciona en Ring0 por lo que es bastante restrictivo a no ser que utilizemos el truco del método 4.

Código: Seleccionar todo

mov eax, Device_ID      // 202 h para SICE  o 7A5F h para SIWVID  VXD ID
mov edi, Device_Name  // solo se usa si no tenemos el ID del VXD, en nuestro caso será 0 (NULL)
VMMCall  get_DDB
mov [DDB], ecx           // ecx = DDB si el dispositivo está instalado o ecx = 0 si el VXD no está instalado
Bueno, pues hasta aquí llegan estos tips,trucos,lecciones o como quieras llamarlo de Mr. Silver, espero que sean de vuestro agrado..

Lección-3: Puertas de llamada o CallGates y VxD

Antes de seguir debo decir que la información que he encontrado en la red sobre estos temas ha sido bastante pobre en lo que a explicaciones se refiere, tras un trabajo de investigación y de atar unas cosas con otras presento el resultado de mi investigación que puede no ser correcta del todo y espero que si alguien encuentra algún error me lo comunique. La información que aquí se brinda puede resultar extremamente peligrosa si se hace un uso indebido de ella.

Las siguientes técnicas pueden aplicarse para la contrucción de virus y programas que pueden destruir el sistema sin que el usuario ni tan siquiera reciba ningún mensaje de error. Pero también pueden ser utilizadas para crear herramientas que nos sean de gran utilidad en el noble arte del cracking, como Volcadores de Memoria,API Hookers,Monitoreadores y Parcheadores de Procesos...

Puertas de Llamada:

Puertas de llamada, para conseguir el nivel de privilegio máximo sobre el S.O. Un momento, que estás diciendo ? Y que es eso de privilegios y puertas? . Pues los niveles de privilegio de una aplicación son como una especie de autorización que el S.O concede a cada tarea para que trabaje. En principio un PC ( hablo de un 386 o superior ) puede tener 4 niveles de privilegio y a mayor privilegio menos nivel, es decir, el nivel máximo de privilegio que una aplicación puede tener es el nivel 0 ( o Ring0 en inglés ), y el mínimo el nivel 3 ( Ring 3 ).

Si no me equivoco Windows no utiliza los 4 niveles de privilegio sino que solo usa el 0 y el 3, siendo estos el de supervisor y el de usuario respectivamente. En nivel 0 ( Ring 0 ) se ejecuta el código del S.O y normalmente el código que utilizan los controladores de dispositivos ( Drivers ), conocidos estos también por tener la extensión VXD, por último el resto de aplicaciones se ejecutan en nivel 3.

Una aplicación en nivel 3 ( Ring 3 ) está muy limitada en cuanto a acceso directo al hardware se refiere, las instrucciones de E/S ( IN OUT y otras como CLI STI ) son exclusivas del nivel 0 ( Ring0 ) y una aplicación en nivel 3 no puede ( teoricamente jejeje... ) acceder directamente a la memoria usada por otra aplicación, o por poner un ejemplo no puede acceder a una posición de memória que esté fuera de la memória asignada a esa tarea. Para nosotros esto significa la imposibilidad ( teórica ) de poder alterar el código del programa en caliente, interceptar las llamadas al API y monitorizarlas con una función propia, y más imposible aún es alterar el propio funcionamiento del S.O.

Todo esto antiguamente no existía ya que el DOS, no distinguía entre privilegios ni mariconadas de protección de memoria, si uno quería machar la memória donde se encontraba el código del COMMAND.COM era libre de hacerlo ( recuerdo cuantas veces se me ha colgado el DOS por esto ). Algunas de las cosas que he explicado anteriormente si son posibles de realizar mediante el uso de algunas funciones del API de Windows, pero seguimos estando limitados en muchos aspectos.

La solución a nuestro problema se llama Puertas de Llamada o Call Gates, he de decir que conocía la existencia de este método pero no sabía bien bien de que iba hasta que me topé con él mientras analizaba un sistema de protección comercial que no viene al caso. La aplicación en cuestión utilizaba el método de comparación de vectores de la INT 1 y la INT 3 descrito en la lección 2. Y que puede ser burlado fácilmente con la utilidad Bang!. Aún así la aplicación se me resistía a funcionar con mi estimado SoftIce en marcha, así que me decidí a trazar la aplicación paso a paso analizando los posibles comportamientos anti-debugging que pudiera encontrar. Tras una breve sesión de trazado ( que aburrido es trazar código ! ), me encontré con algo que me hizo sospechar bastante. Primero encontré una llamada a la función VMM Get_DDB ( Get Device Description Block ) seguida de Test_Debug_Installed, estas dos funciones forman parte del VXD VMM ( Virtual Memory Manager ), son privilegiadas y solo pueden ser ejecutadas por código que corra en un nivel de privilegio igual a 0, además se suelen usar para detectar el SoftIce ( ver lección 2 ) .

Entonces si las aplicaciones se ejecutan en nivel 3, ¿ Cómo es posible que se pudieran estar ejecutando estas dos funciones privilegiadas ?. Pues de alguna manera la aplicación debía de poder entrar en Ring 0, y para ello debía utilizar algún método. Primero pensé que era posible que la aplicación estuviera utilizando algún método como el que se describe en la lección 2, este método se basa en reemplazar un vector de interrupción de la IDT ( Interrupt Descriptor Table ), por la dirección del código que queremos que se ejecute al llamar a esta interrupción, pero tras algunas comprobaciones deseché esta teoría. Porqué? ¿ Pues entonces como leches se lo montan para ello?

Pues me puse a trazar a lo retro ( de adelante hacia atrás usando la pila que para eso está ) y tras encontrar la parte de código que hacía entrar a la aplicación en Ring 0, me llevé una grata sorpresa, esto fue lo que encontré:

Código: Seleccionar todo

pushad                                   // guarda el contenido de los registros

push ebx                                // y empuja EBX a la pila, esto lo hace así para no tener que guardar el
                                              // resultado de la siguiente instrucción en un buffer aparte
sgdt   fword ptr [esp-2]         // lee el registro de la Tabla Global de Descriptores ( GDT ) de la tarea
                                              // actual en la pila
pop ebx                                 // carga base de la GDT desde la pila

xor   eax, eax                        // borra EAX

sldt   eax                               // y carga el registro de la Tabla Local de Descriptores en AX ( Local
                                             // Descriptor Table Register LDTR )
and  al, F8                            // borra nibble alto y ultimo bit del nibble bajo, esto elimina los bits de
                                             // privilegio RPL y TI del selector y deja el índice a la GDT en AX

add  eax, ebx                        // suma la base de la GDT al índice

mov  ch, byte ptr [eax+7]    // usa resultado como índice para llenar CH

mov  cl, byte ptr [eax+4]     // y CL

shl  ecx, 10                          // desplaza ECX 16 bits a la izquierda, quedando el valor anterior de CX
                                            // en la parte alta de ECX y CX a 0

mov  cx, word ptr [eax+2]  // lee el resto de la base de la LDT en el word bajo de ECX

lea  edi, [ecx+8]                  // suma a la base de la LDT 8, esto lo hace para modificar el segundo
                                            // descriptor de segmento de la LDT tal y como sigue
cld                                       // borra flag de dirección ( por seguridad, para que se copie hacia
                                            // adelante
mov  eax, esi                       // copia dirección de código a ejecutar en Ring 0

stosw                                   // y guarda la parte baja de esa dirección, en el campo límite del
                                            // descriptor
mov  eax, EC000028          // establece los atributos en 0xEC00 y el selector en 0x28
stosd                                    // lo guarda

shld  eax, esi, 10                 // desplaza parte baja de EAX a la parte alta y llena la parte baja con los
                                           // bits más significativos de ESI
stosw                                  // y guarda parte baja de EAX, esto pertenece a la parte alta de la
                                           // dirección a la que saltar en Ring 0 + los atributos + los derechos de
                                           // acceso
popad                                 // restaura los registros y salta a la dirección de código a ejecutar en
                                          // Ring 0, el selector es 0xF, ya que se modificó el segundo selector de la
call 000F:0000000           // LDT , para volver de esta llamada se debe usar la instrucción RETF ya
                                          // que es una llamada lejana la que se realiza, en la rutina también deben
                                          // salvarse los selectores actuales de la CPU para ello usamos PUSHAD y
                                          // POPAD
ret
Explico brevemente lo que hace este código. Se obtien el registro GDTR mediante la instrucción SGDT para la tarea actual, se obtiene el registro LDTR mediante la instrucción SLDT, se enmascara para obtener el índice a la GDT y se calcula la dirección del descriptor de segmento en EDI. Se modifican los datos del descriptor para que apunten a la dirección de código almacenada en ESI ( esta dirección es una dirección dentro de la aplicación en Ring 3, que nos servirá de puerta de entrada al código en Ring 0 ), y también se modifican sus atributos y derechos de acceso.

Por último se llama a la puerta mediante CALL 000F:00000000 , (si entramos dentro de la llamada el código siguiente se estará ejecutando en Ring 0;), y tal y como yo esperaba es el que se encarga de llamar a Get_DDB y Test_Debug_Installed.


Al entrar en modo protegido, deben estar residiendo en memoria principal las tablas de descriptores, que contienen las referencias precisas para los segmentos que va a usar el procesador. Un sistema multitarea se compone de un área global, en la que residen todos los objetos ( segmentos ) comunes a todas las tareas, y un área local para cada tarea, con los segmentos propios de cada una. Cada segmento del área global está definido por un descriptor, existiendo una tabla llamada TABLA DE DESCRIPTORES GLOBALES o TABLA GLOBAL DE DESCRIPTORES ( GDT ), que contiene todos los descriptores del área global.

Asimismo existe una tabla para cada tarea, que recoge todos los descriptores de los segmentos de cada una de ellas. Se trata de las TABLAS DE DESCRIPTORES LOCALES ( LDT ) o TABLAS LOCALES DE DESCRIPTORES. Existirán tantas LDT como tareas soporte el sistema. En un momento determinado el 386+ estará ejecutando una tarea concreta y tendrá activas la GDT y la LDT correspondiente a la tarea en curso. Dos registros internos de la CPU, manejados por el programador de sistemas, apuntan a la base de la GDT y a la base de la LDT activa, denominándose GDTR y LDTR, respectivamente. La GDT y la LDT actuán como segmentos del sistema y sólo son accesibles por el sistema de explotación. La estructura interna de una tabla de descriptores se muestra en la siguiente tabla y puede contener un máximo de 8K descriptores de 8 bytes cada uno. La LDT es una tabla local propia de la tarea en curso y una conmutación de tarea provocará automáticamente el cambio de la LDT a través de la modificación del valor en el registro LDTR.
+N x 8 DESCRIPTOR N
…. …...

+ 16 DESCRIPTOR 2
+ 8 DESCRIPTOR 1
0 DESCRIPTOR 0
Nótese que los registros GDTR y LDTR apuntan al descriptor 0 de sus respectivas tablas.

Y por último la estructura de los registros GDTR y LDTR.
// estructura para GDTR

typedef struct
{
word limit; // tamaño de la tabla
dword base; // dirección base del descriptor 0

} FPWORD;

// estructura para el registro LDTR

word selector; // 16 bits que actuan como un selector de un descriptor de la GDT
Un selector se define de la siguiente manera:
( 15 – 3 ) ( 2 ) ( 1 – 0 )

ÍNDICE TI RPL
Donde :

ÍNDICE : Apunta a una de las entradas a la tabla de descriptores seleccionada con TI .

TI ( Table Indicator ) : Este bit es el indicador de la tabla, cuando TI = 1, se selecciona la LDTn, mientras que, si TI = 0, se hace referencia a la GDT.

RPL : Este campo es el valor del nivel de privilegio del segmento, es decir, es el nivel de privilegio del peticionario ( PL ). Puede ser 0 ( 00 en binario ) o 3 ( 11 en binario ).

Comprobemos ahora el valor del segmento del código anterior. Se establece el selector a 0x0028, que en binario es 101000, vemos pues que los bits 1 a 0 son 0b ( petición para un nivel de privilegio 0 ), y el bit TI es 0b también, por lo que estamos haciendo referencia a la GDT. Por último los bits 15 a 3 valen 101b que en deimal es 5, por lo tanto se hace referencia al 5 descriptor de la GDT ( la dirección de este descriptor sería ( GDTR BASE + ( 5 * 8 ) ).

Según los libros que he podido consultar un descriptor de segmento es una estructura de datos formada por 8 bytes, estos bytes contienen los parámetros que definen completamente el segmento referenciado, es decir: la base, el límite, los derechos de acceso y sus atributos.

BITS 31 – 24 23 – 20 19 – 16 15 – 7 7 – 0

DIRECCIÓN BASEG D/B 1 AVLLÍMITEP DPL S TIPO A BASE
N + 4 bits 1 1 1 bits 1 2 1 3 1

(31-24) bit bit bit bit (19-16) bit bits bit bits bit ( 23 – 16 )
----------------------------------------------- --------------------------------------------
DIRECCIÓN BASE ( 15 – 0 ) LÍMITE ( 15 – 0 )
N


Según esto podríamos definir la estructura de un descriptor como lo siguiente :
// estructura de un descriptor

typedef struct {

WORD limit_low ;
WORD base_low ;
BYTE base_m ;
BYTE limit_high ;
BYTE base_high ;

} Descriptor ;
Pero por alguna razón que desconozco, a la hora de la verdad ( en el código ) se usa la siguiente estructura:
// Compuertas del 386+

typedef struct {

WORD offs_low ; // word alto con la dirección de la memoria a la que apunta el
// descriptor / compuerta

WORD selector ; // selector
WORD attributes ; // atributos
WORD offs_high ; // word bajo con la dirección de la memoria a la que apunta el
// descriptor / compuerta

} Compuerta ;
El tamaño es el mismo pero no así el asignamiento de bytes de cada campo del descriptor / compuerta.

BASE : Campo de 32 bits que contiene la dirección lineal donde comienza el segmento.

LÍMITE : Campo de 20 bits que expresa el tamaño del segmento. Ya que con 20 bits el tamaño máximo es de 1 MB, hay otro bit complementario en el campo de atributos, llamado Granularidad. “ G “ , que indica si el límite está expresado en bytes ( G = 0 ) o en páginas ( G = 1 ). Si fuera en páginas el tamaño máximo del segmento sería de 1M x 4 Kb = 4 GB.

En verde los atributos : Es un campo de 4 bits de los cuales uno de ellos debe estar a 0 para mantener la compatibilidad con los procesadores superiores como los 486 y los Pentium.

G ? GRANULARIDAD : Los 20 bytes del campo límite del descriptor indican el tamaño del segmento, que estrá expresado en bytes si G = 0, y en páginas si G = 1.

D / B ? DEFECTO / GRANDE : En los segmentos de código el bit D ( Defecto ) y en los segmentos de datos este mismo bit llamado B ( Grande ), permite distinguir los segmentos nativos de 32 bits para el 386+, de los que pertenecen al 286. Así se mantiene una compatibilidad total con el software creado para el 80286, sin penalizar las instrucciones que aporta el 386+.

AVL ? DISPONIBLE : Este bit está a disposición del usuario para poder diferenciar ciertos segmentos que contengan un tipo determinado de información o que cubran alguna función específica.

En rojo los Derechos de Acceso :

A ? ACCEDIDO : Este bit se pone a 1 cada vez que el procesador accede al segmento.

P ? BIT DE PRESENCIA : Indica si el segmento al que referencia el descriptor está cargado, o sea, se halla presente en la memoria principal ( P = 1 ) , o bien, está ausente ( P = 0 ).

DPL ? NIVEL DE PRIVILEGIO : Indica el nivel de privilegio del segmento al que se referencia el descriptor. Su valor puede variar entre el 0 y el 3 y consta de dos bits.

S ? TIPO DE SEGMENTO : Si S = 1, el segmento correspondiente al selector es “ normal “, o sea, un segmento de código, de datos o de pila. Si S = 0 , se refiere a un segmento del sistema, que referencia a un recurso especial del sistema, como puede ser una Puerta de Llamada ( este interesa;)), un segmento TSS, etc...

TIPO: Los tres bits de este campo distinguen en los segmentos normales si se trata de uno de código, de datos o de pila. Además determinan el acceso permitido.

TIPO

E C R

Si el bit E del campo tipo es 1, TIPO se define como :

TIPO

1 C R

Donde :

C ? AJUSTABLE : Si C = 0 , al ser accedido el segmento no cambia su nivel de privilegio, si C = 1 , se llama segmento ajustable, porque, cuando se accede a él, su nivel de privilegio toma el valor del que tiene el segmento que lo ha pedido.

R ? LEÍBLE : Si R = 1 el segmento de código se puede leer. En ningún caso se puede escribir un segmento de código.

Cuando E = 0 y se hace referencia a un segmento de datos, los otros dos bits de TIPO tienen el siguiente significado:

TIPO

0 ED W

Donde :

ED ? EXPANSIÓN DECRECIENTE : Si ED = 0, se trata de un segmento de datos normal, lo que supone que el crecimiento del mismo se realiza incrementando el valor de la dirección .
Cuando ED = 1, se trata de un segmento de pila pues su crecimiento se efectua decrementando el valor de la dirección que apunta a su cima.

W ? ESCRIBIBLE : Si W = 1 el segmento de datos se puede leer y escribir, mientras que, si W = 0, sólo se puede leer.

Tal y como podemos ver en la estructura el parámetro más interesante es el de los atributos ( en rojo ), en el programa en cuestión se establecen los privilegios a 0xEC00 , veamos que significa esto.

0xEC00 en binario ? 1110110000000000, si comprobamos cada campo de los atributos..

P ( bit 15 ) ? Está a 1, por lo tanto el segmento al que hace referencia el descriptor está cargado, esto es lógico ya que el código que ejecutará la aplicación se encuentra en un segmento ya cargado, el de la aplicación en Ring 3.

DPL ( bits 14 – 13 ) ? Indica el nivl de privilegio, vale 11, que es 3, por lo tanto Ring 3.

S ( bit 12 ) ? Está a 0, el segmento correspondiente al selector es un segmento de sistema que pertenece a un recurso especial como una Puerta de Llamada, correcto, es lo que buscabamos.

TIPO ( bits 11 – 9 ) ? Bit 11 a 1, indica que se hace referencia a un segmento de código, los bits 10 y 9 indican que el segmento es ajustable ( adoptará el nivel de privilegio del segmento que lo solicitó, que tal y como vimos era 0x0028 y tenía un RPL de 0 ), y que no es legible.

Visto el funcionamiento de los selectores, descriptores, GDT y LDT, podemos comprender el funcionamiento del código usado por el programa para entrar en Ring 0. Otro punto a tener en cuenta es que deberíamos hacer una copia de el descriptor antiguo antes de modificarlo para luego dejarlo tal y como estaba todo, he realizado diversas pruebas y si no restauramos el descriptor no sucede nada ya que solo se salta a la dirección que marca el descriptor cuando se accede directamente a ella, pero es de buena educación dejar las cosas como estaban. Por ultimo aquí teneis el código fuente en C que muestra como saltar a Ring 0, y llamar a la función del VXD VMM Test_Debug_Installed, y si se encuentra debugger se cuelga la máquina intencionadamente llamando a la INT 19.

La INT 19, reinicia el sistema sin limpiar la memoria y sin restaurar los vectores de interrupción. Debido a que los vectores son preservados, esta interrupción causará un cuelgue del sistema si cualquier programa captura alguno de los vectores entre el 00h y el 1C h, particularmente la INT 8. Este cuelgue sucederá siempre bajo Windows ya que estos vectores están modificados por Windows.

Desafortunadamente esta técnica no funciona bajo Windows NT y el sistema generará un error de aplicación, de todas formas yo lo he probado bajo Windows 98 y Windows Me, y funciona de maravilla, con lo que deduzco que probablemente funcionará bajo Windows 95.

Código: Seleccionar todo

# include < stdio.h >
# include < windows.h >

char caption[ ] = { “ Test Debug by Mr. Silver / WKT! };

// alinea las estructuras a BYTE
# pragma pack (1)
// estructura para la base de la GDT

typedef struct
{

 WORD limit ;
 DWORD base ;

} FPWORD ;

// estructura de un descriptor

typedef struct  {

WORD limit_low ;
WORD base_low ;
BYTE  base_m ;
BYTE  access ;
BYTE  limit_high ;
BYTE  base_high ;

  } Descriptor ;
// Estructura de un salto lejno, para las CALLGATES

typedef struct  {

DWORD  offset 32 ;
WORD  seg ;

} FARJMP ;

// compuertas del 386 +

typedef struct  {

WORD  offs_low ;
WORD  selector ;
WORD  attrib ;
WORD  offs_high ;

 } Compuerta ;

   __declspec ( naked ) void MyProc ()

{
   _asm {

      xor eax, eax
      int  0x20             // esto ejecuta la llamada VXD Test_Debug_Installed, las VxdCalls se utilizan
                                    a traves de la INT 20, tras la interrupción siguen dos WORDS, el primero
   _emit  0xC1          // identifica el número de servicio y el siguiente el identificador VXD, en este
   _emit  0x00          // caso se usa el servicio 0x00C1 del VXD VMM, para obtener una descripción
   _emit  0x01          // de los servicios y sus identificadores.
   _emit  0x00          // mirate la lista de interrupciones de Ralf Brown
  
   jz  NoDebug        // hay debugger ??

   int  0x19              // si, colgamos la máquina a saco Wink
   NoDebug ;
   retf                       // no, salimos

 }
}

  __declspec ( naked ) void TestDebugVXD ( void * addr )

  {

   DWORD  GateAddr ;          // dirección de la puerta
   Compuerta OldGate ;          // estructura donde copiar valores actuales
   FPWORD  GDT ;
   FARJMP  CallGate ;

   _asm
 {

    push ebp
    mov ebp, esp
    sub esp, __LOCAL_SIZE     // con esta definición el procesador calcula automáticamente el
                                                  // tamaño de pila necesario para las variables locales que usa la
                                                  // función
  }

   _asm  {

     pushad                               // guarda el estado de los registros antes de ejecutar nada
     mov ebx, [addr]
     mov esi, ebx                       // copia dirección de la función a ejecutar en Ring 0
     sgdt fword ptr GDT
     mov ebx, GDT.base
     xor eax, eax                        // borra EAX
     sldt  ax                                // y carga el registro de la Tabla Local de Descriptores en AX ( LDT )
     and  al, 0xF8                      // borra nibble alto y último bit del nibble bajo
     add eax, ebx                       // suma la base de la GDT al valor calculado de la LDT
     mov ch, [eax+7]                // usa resultado como índice para llenar CH y CL lo desplaza 16 bits
     mov cl, [eax+4]                 // a la izquierda
     shl  ecx, 0x10
     mov cx, [eax+2]                // carga parte baja de la base, con lo anterior se obtiene la base de la
                                               // LDT  en ECX
     lea  edi, [ecx+8]                // y carga en EDI la base calculada más  8

     mov dword ptr [ GateAddr] , edi    // hace una copia de la dirección de la compuerta
     cld                                                   // borra flag de dirección para establecer
     mov  cx, word ptr [edi]                   // copia antigua parte baja
     mov  [OldGate.offs_low], cx          // del desplazamiento de la compuerta
     mov  eax, esi                                   // guarda parte baja de la dirección de la función que se
                                                             // ejecutará en Ring 0
     stosw                                               // como límite de segmento
     mov  ecx, [edi]                               // guarda antiguo
     mov dword ptr [OldGate.offs_low+2]    // selector y atributos
     mov  eax, 0xEC000028                           // y establece la base a 28 y los niveles de privilegio a
                                                                      // EC h
     stosd
     shld eax, esi, 0x10                         // desplaza parte baja de eax a la parte alta y llena la parte baja
                                                           // con los bits más significativos de ESI
     mov cx, word ptr [edi]                  // copia antigua parte alta
     mov [OldGate.offs_high] , cx       // del desplazamiento de la compuerta
     stosw                                             // y guarda parte baja de EAX, esto pertenece a la parte alta de
                                                           // la dirección a la que saltar en Ring 0
     mov  CallGate.seg, 0x0F
     mov  CallGate.offset32 , 0x0
     call  fword ptr CallGate
     mov  edi, dword ptr [GateAddr]    // restaura vieja compuerta
     mov  ax, [OldGate.offs_low]
     mov  [edi] , ax
     stosw
     mov  eax, dword ptr [OldGate.offs_low+2]
     stosd
     mov  ax, [OldGate.offs_high]
     stosw
     popad                                          // restaura los registros

   }
      _asm  {

       mov esp, ebp
       pop ebp
       ret

    }
}  

void main ()
 
  {
      int resp ;

resp = MessageBox (NULL, “Esta aplicación salta a Ring 0 y comprueba la existencia de un debugger. Si existe debugger la máquina se colgará irremediablemente. ¿ Desea continuar ?” , caption , MB_YESNO) ;

   if  (resp == IDYES)
 
{

TestDebugVXD (&MyProc) ;

MessageBox (NULL, “ No hay ningún debugger activo “ , caption , MB_OK) ;

  }

}
Bueno,pues este es el último texto que tengo de Mr. Silver / WKT!,espero que les haya gustado y que investiguen un poco ya que como el dice no funciona en NT,a ver si son capaces de hacerlo andar en NT...

Aún quedan algunos pequeños tips sobre técnicas antidebugging,pero son de otra persona, a ver cuando tengo tiempo y las pongo...

Saludos !

PD: perdon por la parte donde va el esquema de colores,pero creo que no esta la etiqueta table y no sale correctamente....
Imagen


Solo lo mejor es suficiente...
Ejemplo mas claro imposible de como saber si estan debugeando tu aplicacion:

Código: Seleccionar todo

Option Explicit
 
Private Declare Function IsDebuggerPresent Lib "kernel32" () As Long
 
Private Sub Form_Load()
    If IsDebuggerPresent <> 0 Then
        MsgBox "Si estan debugeando mi programa"
    Else
        MsgBox "No estan debugeando mi programa"
    End If
End Sub
 

PD: el code esta en vb6

salu2!
Imagen
que buen trabajo estás haciendo cLn...! y muy interesante estas lecciones, las iré leyendo =P.

un saludo desde el sur de la comunidad
Soy un camaleón, en tu cama, leona ♪
Responder

Volver a “Manuales”