Random IRC quote :      <h> yo le metia algo entre las piernas para equilibrar el tema <soybobo> un cordero asao

Rootkit Arsenal, Installing a Call Gate

Buenas, leyendo el libro “The Rootkit Arsenal: Escape and Evasion in the Dark Corners of the System” quería matizar un par de cosas sobre el capítulo “Hooking the GDT – Installing a Call Gate”. Al final del artículo se incluye un POC driver con el soporte del WalkGDT para varios COREs entre otras.

Una Call Gate es un mecanismo en la arquitectura x86 de Intel para cambiar el nivel de privilegio de la CPU cuando se ejecuta una función predefinida llamada mediante una instrucción CALL/JMP FAR.

Una llamada a una Call Gate permite obtener un privilegio superior al actual, por ejemplo podemos ejecutar con un CALL FAR en ring3 una rutina en ring0. Una Call Gate es una entrada en la GDT (Global Descriptor Table) o en la LDT  (Local Descriptor Table).

Windows no utiliza Call Gates para nada en especial, pero hay malware como el gusano Gurong.A, que instala una Call Gate a través de \Device\PhysicalMemory para ejecutar código en ring0. Un artículo que ya hablaba sobre el tema es “Playing with Windows /dev/(k)mem” de crazylord publicado en la Phrack 59.

Hoy en día no se puede hacer tan fácilmente lo de acceder a /Device/PhysicalMemory, recomiendo leer la presentación de Alex Ionescu  en la RECON 2006 “Subverting Windows 2003 SP1 Kernel Integrity Protection“. También hay ejemplos por la red que usan la API ZwSystemDebugControl para instalar una Call Gate, pero como comenta el artículo de Ionescu tampoco funciona hoy en día (aunque siempre hay técnicas para reactivarlos de nuevo).

IMHO, la mejor manera para instalar una Call Gate es un driver como lo hace el ejemplo del libro Rootkit Arsenal, ahora voy a explicar el ejemplo que trae y añadir algunas cosas que veo que faltan:
Una entrada en la GDT tiene esta pinta:

typedef struct _SEG_DESCRIPTOR
{
WORD size_00_15;
WORD baseAddress_00_15;
WORD baseAddress_16_23:8;
WORD type:4;
WORD sFlag:1;
WORD dpl:2;
WORD pFlag:1;
WORD size_16_19:4;
WORD notUsed:1;
WORD lFlag:1;
WORD DB:1;
WORD gFlag:1;
WORD baseAddress_24_31:8;
} SEG_DESCRIPTOR, *PSEG_DESCRIPTOR;

Y una Call Gate es un tipo de entrada en la GDT con el siguiente aspecto:

typedef struct _CALL_GATE_DESCRIPTOR
{
WORD offset_00_15;
WORD selector;
WORD argCount:5;
WORD zeroes:3;
WORD type:4;
WORD sFlag:1;
WORD dpl:2;
WORD pFlag:1;
WORD offset_16_31;
} CALL_GATE_DESCRIPTOR, *PCALL_GATE_DESCRIPTOR;

offset_00_15: es la parte baja de la dirección de la rutina que se ejecutará en ring0, offset_16_31 es la parte alta.

selector: especifica el segmento de código, con el valor KGDT_R0_CODE (0x8), la rutina se ejecutará con privilegios de ring0.

argCount: es el número de argumentos de la rutina en DWORDs.

type: es el tipo de descriptor, para una Call Gate de 32 bits necesita el valor 0xC

dpl: es el privilegio mínimo que debe tener el código que llama para poder ejecutar la rutina, en te caso 0x3, ya que será llamada por una rutina de ring3.

Los pasos para crear una Call Gate son:

  1. Construir la Call Gate que apunte a nuestra rutina.
  2. Leer el registro GDTR para poder encontrar la GDT usando la instrucción: SGDT. El registro GDTR tiene la siguiente pinta:
    typedef struct _GDTR
    {
    WORD  nBytes;
    DWORD baseAddress;
    } GDTR;

Para obtener el número de entradas en la GDT basta con un GDTR.nBytes / 8.

  1. Buscar una entrada libre en la GDT.
  2. Escribir la Call Gate.

Para llamar a la Call Gate solo es necesario hacer un CALL FAR al selector de la GDT, es decir si hemos introducido la Call Gate en la entrada 6 de la GDT, la aplicación de espacio de usuario deberá ejecutar un CALL FAR 006:00000000. La otra parte del FAR CALL no sirve para nada pero debe estar en la instrucción.

La rutina de la Call Gate debe salvar los registros: EAX, ECX, EDX, EBX, EBP, ESP, ESI, EDI, EFLAGS y FS. Además debe desactivar las interrupciones con CLI. El selector de FS debe ser 0x30, después solo es necesario restaurar los registros y activar las interrupciones con STI y un RETF si se desea volver a ring3. Todo esto está sacado de nt!KiDebugService() por si te lo estás preguntando.

Bueno y ahora es el momento de los matices, el POC code del libro no tiene en cuenta la posibilidad de existir varios COREs, esto quiere decir que solo es capaz de instalar la Call Gate en el CORE que toque cuando se cargue el driver y la GDT del otro CORE queda intacta, el problema es que si la aplicación de espacio de usuario hace un FAR CALL estando en otro CORE donde no existe la Call Gate no funcionará.

En Windows es sencillo controlar esto con dos APIs: para obtener el número de COREs se puede usar un simple

GetSystemInfo:
void WINAPI GetSystemInfo(
__out  LPSYSTEM_INFO lpSystemInfo
);

Para el número de COREs lógico se puede usar GetLogicalProcessorInformation.
SYSTEM_INFO tiene la siguiente pinta:

typedef struct _SYSTEM_INFO {

DWORD     dwNumberOfProcessors;

}SYSTEM_INFO;

Con el campo dwNumberOfProcessors podemos realizar un bucle para ir CORE por CORE añadiendo la Call Gate, también se puede forzar al driver a instalar la Call Gate en el primer core (1) y que la aplicación de espacio de usuario solo se ejecute en el core 1, esto se consigue con la API: SetThreadAffinityMask, que es asín:

DWORD_PTR WINAPI SetThreadAffinityMask(
__in  HANDLE hThread,
__in  DWORD_PTR dwThreadAffinityMask
);

Pasándole un GetCurrentThread() y como AffinityMask el valor 1,

Affinity = 1;
SetThreadAffinityMask( GetCurrentThread(), Affinity );

Ojito, DWORD_PTR no es un puntero a DWORD, se pasa por valor.

Si se hace un for con el número de procesadores y con la variable índice (el primero core es 1 no 0) como Affinity puedes instalar una Call Gate en todos los cores.

En el driver que adjunto como POC desde el driver muestro la GDT de todos los COREs para mostrar lo que digo, solo saldrá la Call Gate en un CORE.

Para hacer esto desde un driver es necesario un:

ZwQuerySystemInformation( SystemBasicInformation, &amp; system_basic_information, sizeof( system_basic_information ), NULL );

Para obtener el número de cores y un:

ZwSetInformationThread( (HANDLE) -2, ThreadAffinityMask, &amp; AffinityMask, sizeof( AffinityMask ) );

Para cambiar de core, -2 es el equivalente del GetCurrent…

Otra cosa rara del POC del libro es que pasaba la GDT por valor y no el puntero a PrintGDT y eso me daba problemas, lo he cambiado para que se pase como PSEG_DESCRIPTOR y todo funciona sin problemas.

Y esto es todo, el código es bastante claro, espero que se haya entendido, un saludo desde 48bits.

Descargar el driver desde aquí: http://www.48bits.com/files/cgaty.rar

Driver probado en: XP y VISTA (bin driver: WinDDK 6001.17121 & XP x86 Free build).

Algunas lecturas sobre call gates:

http://www.phrack.com/issues.html?issue=59&id=16
http://en.wikipedia.org/wiki/Call_gate

http://www.intel.com/design/processor/manuals/253668.pdf
http://ricardonarvaja.info/WEB/OTROS/TUTES%20SACCOPHARYNX/
http://members.fortunecity.com/blackfenix/callgates.html

14 Comentarios para “Rootkit Arsenal, Installing a Call Gate”

  1. Comment por pluf | 01/08/10 at 8:57 am

    guapo!! 🙂

  2. Comment por mikelet | 01/08/10 at 10:29 am

    Que aria io sin bosotros!!

  3. Comment por Ruben | 01/08/10 at 11:10 am

    Primer post también de David, que ya tiene tablas en esto. Hemos empezado el año con mucha actividad, mola. Creo que va a estar interesante el 2010.
    Muy bien explicado!

  4. Comment por plusvic | 01/08/10 at 12:23 pm

    Guapo, guapo. Menuda racha que lleva el blog.

  5. jon
    Comment por jon | 01/08/10 at 3:52 pm

    Gran trabajo david! 😉 Hoy día es fundamental pensar en mas de un core al programar cualquier cosa. Me extraña que al autor se le haya escapado este tema por que el PoC del resto de capitulos recuerdo que cuidaba el tema. A ver si en el futuro podemos aportar más sobre el tema de rootkits que no parece haber mucho movimiento!

  6. Comment por kktmp1 | 01/08/10 at 4:49 pm

    Buen aporte Dreg.. muchas gracias. Ya veo que esta todo dios leyendo el libro de Blunden. Realmente es muy bueno. Estoy deseando ver con que nos sorprente a primeros de Febrero en el BH DC2010. «An Uninvited Guest (Who Won’t Go Home)»… promete.

    Sobre rootkits.. al loro también a la evolucion del Stoned Bootkit. Un RK que se instala en el MBR y que ya es capaz de leer ficheros en Raw sobre espacio no particionado o sectores marcados como defectuosos… esta misma semana Peter ha publicado la version 2.

  7. Comment por David Reguera | 01/08/10 at 6:02 pm

    Gracias chicos, he mandado un mail a Blunden por si hacen alguna revisión o algo así que puedan actualizarlo.

  8. Comment por LocoDelAssembly | 01/08/10 at 7:19 pm

    Hola, me parece que esta línea está mal:
    AffinityMask = j + 1;

    Debería ser esta:
    AffinityMask = 1 << j;

    Sino, en la primera iteración se activa el primer procesador lógico, en la segunda iteración se activaría el segundo (perfecto para Core2 Duo hasta acá :P), en la tercera ya se activarían el primero y el segundo al mismo tiempo.

    Ya que he escrito aprovecho para dar infinitas gracias por este blog, siempre excelente!

  9. Comment por David Reguera | 01/08/10 at 10:29 pm

    Bueno esto era a modo de ejemplo, solo vale para dos COREs, en breve estará subido el driver con el AffinityMask = 1 << j; en el src y el .sys, el paper en inglés y además un comentario hablando sobre lo de como manejar la máscara.

  10. Comment por Juan C. Montes | 01/12/10 at 8:22 am

    Buen texto David, 🙂

    Pero para que usar un driver para meter una callgate… cuando ya estas en ring0?

  11. Comment por David Reguera | 01/12/10 at 10:03 am

    Bueno, quizá por que luego tengas un APP de userspace que tire del callgate para quien depure la app le cueste más o por que es un friki, yo que se xD.

    Pero vamos, la idea de enseñar el tema de las Call Gates desde un driver en ring0 es útil por que el código siempre funciona y no hay que andar con lo de habilitar el pysical o la api de debug que ya no funciona directamente.

    No obstante la intención del POST era simplemente matizar el tema de los call gates siguiendo el ejemplo de rootkit arsenal, que no metía lo del affinity y cosas así.

    Un saludo.

  12. Comment por Alon | 01/18/10 at 4:47 am

    Muy bueno el post!!! Enhorabuena tb x tu trabajo en el RKU.

    Un saludo

  13. Comment por SparK | 02/07/10 at 2:35 am

    Buen post david!

    Interesante la técnica y teniendo en cuenta varios cores.

    Un abrazo.

  14. Comment por David Reguera | 02/19/10 at 5:19 pm

    Aclaración sobre lo del selector y el CALL FAR 6,

    En la GDT el selector 0x320, sería en binario 1100100000 = Entry:1100100(GDT) TI=0 RPL=00.

    Así pues es necesario un CALL FAR 0x320 para ejecutar una Call Gate en la GDT (TI = 0) en la entrada 100 (1100100) y el RPL 00.

Se han cerrado los comentarios