IDT Sniffer

février 26th, 2009 at 04:27 admin

Certains disent que je suis mort, d’autres que j’ai muté pour devenir reverser Java voir même spécialiste UML. Que nenni j’ai tenté de faire le gens ordinaire pendant quelques temps, vous savez : faire du social, parler à des gens, sortir et toutes ces conneries. Cela ne m’a pas beaucoup réussi donc ‘back to the roots’. Pour la peine vous prendrez bien un peu de bon son pour réveillez vos neurones. Du bon post-metal sludge avec Rosetta et Red In Tooth And Claw :


Red in tooth and claw

Une fois revenu aux vraies valeurs, c’est à dire IRC et les trollkores. J’ai beaucoup discuté avec Babo0n sur la possibilité de crée un outil pour surveiller l’IDT notamment lorsque celle-ci est modifiée par des modules noyau qui servent à la protection de binaire comme Themida ou Starforce. Ce post présente juste les réflexions sur l’implémentation d’un outil capable de mettre en place une surveillance de l’IDT, le code devrait être fournit dans un prochain post si j’arrive à le finir :]

On voudrait donc être capable de voir quelles routines de l’IDT sont modifiées par ces drivers puis ensuite de contrôler ceux qui leur est passé. En fait on se placerait comme un proxy filtrant entre l’IDT et le module. Évidemment il n’existe aucun mécanisme natif sur architecture x86 pour faire cela nativement.

Plaçons un peu le contexte. On travaille sur des systèmes Windows en IA-32 SMP avec ou sans PAE. On souhaite hooker uniquement les exceptions pour le moment, les interruptions peuvent aussi être gérées de la même manière mais on s’en fou. Nous n’avons pas droit aux jeux d’instruction VMX et SVM, donc pas de virtualisation pour des raisons de portabilité matérielle.

Juste un rappel sur la forme d’un IDT et de ses structures, cela sera utile pour la suite. Les 32 premières entrées de l’IDT sont réservées pour les exceptions, le reste est pour les interruptions. Voici les détails des exceptions que l’on a :
idt

Chacune des entrées de l’IDT est formée par l’une des gates suivante. En général nous n’avons que des trap ou interrupt gates.
idtgates

A noter que la différence entre une trap gate et une interrupt gate est la gestion de l’IF flag de l’EFlags. Ce flag contrôle le masquage des interruptions, s’il est à 0 plus aucune interruption n’est délivrée sur le core. Lorsqu’une interrupt gate est appelée l’IF flag est automatique mit à 0, il est ensuite remit à 1 lors du retour avec l’instruction IRET. Une trap gate quand à elle ne modifie pas l’IF flag. Sous Windows on ne trouve que des interrupt gates.

Sur un core l’IDT est stocké dans le registre IDTR qui peut être accéder avec l’instruction LIDT et écrit à l’aide de SIDT. L’IDTR est un registre de 48 bits composé d’un couple (base (32), limit(16)) définissant l’IDT.

Pour la suite du post nous considérons uniquement un système non-SMP, nous n’avons qu’une seule IDT à gérer. Bien entendu l’outil final devra supporter des systèmes avec plusieurs cores. L’idée que je présente par la suite pour résoudre ce problème, n’est qu’une parmi d’autres, si vous en voyez de plus simple n’hésitez pas :)

L’idée consiste donc à faire travailler le système sur une copie de l’IDT qui lui sert de tampon pour prévenir les tentatives d’écriture. Ce tampon est alloué en lecture seule. On redéfinit donc l’IDT du système sur ce tampon, lorsqu’une écriture intervient une exception est générée. Notre module regarde la valeur inscrite et note l’adresse de la routine. Ensuite il place sa propre routine à la place de celle qui allait se faire hooker. Cette routine sert à notifier l’utilisateur qu’une exception surveillée à eu lieu et de router cette exception sur le handler du module qui voulait placer le hook.

Maintenant commencons à parler d’implémentation. D’abord définissons quelques termes :

  • FirstIDT : IDT originale, accessible en R/W par défaut sous Win
  • IDT’ : copie de FirstIDT en R/W
  • IDT'' : mapping de IDT’ en RO
  • IDT0 : structure de type IDT vide (0) gérée par notre driver.

On ne peut pas directement copier IDT en RO à cause de l’API kernel. On doit la copier et ensuite la remapper en RO.

Ensuite il faut prendre en compte le bit WP (bit 16) du CR0. Si il est à 1 alors quelque soit le CPL et quelque soit les privilèges des pages leurs droits sont respectés. S’il est à 0 alors on peut écrire dans n’importe quelle page du moment qu’on est en ring0.
En ce qui nous concerne, il nous faut WP=1 pour pouvoir mettre en place une page ring0 en R0. Heureusement c’est le cas par défaut sous Window mais une simple modification de ce bit fait tomber tout l’appli ! Hélas il nous est impossible de contrôler sa modification. Pour info on retrouve la modification de ce bit lors du hooking de SSDT. Voici un tableau récapitulatif :
protect

Extrait intel volume 3A chap 4 :

Starting with the P6 family, Intel processors allow user-mode pages to be write-protected against supervisor-mode access. Setting CR0.WP = 1 enables supervisor-mode sensitivity to user-mode, write protected pages. Supervisor pages which are read-only are not writable from any privilege level (if CR0.WP = 1). This supervisor write-protect feature is useful for implementing a “copy-on-write” strategy used by some operating systems, such as UNIX*, for task creation (also called forking or spawning). When a new task is created, it is possible to copy the entire address space of the parent task. This gives the child task a complete, duplicate set of the parent’s segments and pages. An alternative copy-on-write strategy saves memory space and time by mapping the child’s segments and pages to the same segments and pages used by the parent task. A private copy of a page gets created only when one of the tasks writes to the page. By using the WP flag and marking the shared pages as read-only, the supervisor can detect an attempt to write to a user-level page, and can copy the page at that time.

IDT'' devient l’IDT de notre core. Lors d’une tentative d’écriture une exception de type #PF est levée. Comme en kernel-land il n’existe pas de mécanisme de gestion des exceptions global comme les VEH nous devons mettre en place notre propre #PF handler. Notre module doit donc modifier le page fault handler de IDT'' avant de l’assigner sur le core. Ce handler sert de filtre et permet de savoir si une tentative d’écriture sur IDT'' a eu lieu. Si non alors en appel le gestionnaire de Windows KitTrap0E. Si oui alors on applique un traitement spécial à l’exception. Ce qu’on veut c’est noter la valeur inscrite par le module dans IDT'', or pour le moment nous avons une exception, c’est à dire que l’EIP pointe sur l’instruction fautive du fait qu’on est une fault et non une trap. Il nous faut donc retrouver la valeur qui allait être inscrite dans IDT''. Pour cela il existe plusieurs solutions :

  • On émule l’instruction, pour cela il nous faut une libraire d’émulation x86 ou bien un désassembleur assez puissant pour faire ressortir suffisamment d’information sur la sémantique de l’instruction. Je ne connais aucune lib d’émulation x86 publique, par contre le moteur de disass BeaEngine est un bon candidat. Une fois qu’on a émulé l’instruction on note la valeur qui allait être inscrite au même endroit dans IDT0 ensuite on retourne avec IRET.
  • On exécute l’instruction dans un environnement controlé. En gros on laisse l’instruction être exécuté par le CPU mais on est capable de reprendre la main après. On peut faire cela de plusieurs façons :
    • A l’aide du TF flag (bit 8 ) de l’EFlags on passe en single step. Lorsque l’instruction à finie de s’exécuter on une trap de type #DB. Pour nous cela implique de réaliser les opérations suivantes :
      1. IDT'' passe en R/W.
      2. On récupère l’EIP sur la pile, il pointe sur l’instruction fautive. On calcule sa taille à l’aide d’un LDE ou d’une lib de disass et on ajoute la somme EIP+instrlen dans une variable globale.
      3. On active le TF flags de l’EFlags pour passer en single step.
      4. Retour d’exception avec IRET.
      5. On va décrire le traitement de l’int 1 plus loin.
    • On peut aussi fabriquer une environnement qui nous permet de prendre la main après l’exécution de l’instruction. Avec un code qui fonctionne comme Bee-Lee. Mais cela pose le même problème qu’avec l’utilisation du TF comme nous allons le voir.

On écarte l’utilisation d’une lib d’émulation pour la suite. On garde uniquement la solution avec le TF, vous allez comprendre pourquoi par la suite.

A ce moment l’instruction fautive écrit dans IDT'' (IDT’ est donc lui aussi modifié) et génère une except de type #DB avec l’EIP qui pointe sur l’instruction suivante. En fait précédemment nous avons du hooké le handler pour les #DB (KiTrap01).
On réalise ensuite les traitements suivants :

  1. On vérifie que le saved EIP correspond bien à notre EIP mit en variable globale. Si non alors on appel le handler de Win KiTrap01.
  2. Si l’EIP est celui d’une instruction qui a écrit dans IDT'' alors on note la valeur dans l’entrée équivalente de IDT0. Soit i l’indice de l’entrée inscrite, on sait aussi que sizeof(IDTENTRY)=8, il faut donc entre 8 écritures de 1 byte et 1 écriture de 8 bytes (pouvant être faite à l’aide des instructions MMX par exemple) pour définir une nouvelle entrée dans l’IDT. On compare IDT'' avec IDT pour trouver le nombre de bytes écrit.
  3. IDT0[i] est mit à jour avec le ou les bytes écrit dans IDT''. A chaque entrée de IDT0 est associé un compteur permettant de savoir quand l’écriture de l’entrée est terminée, ce compteur va de 1 à 8.
  4. A partir d’ici 2 cas se présentent :
    1. Le compteur de l’entrée IDT0[i] n’a pas atteint 8. On restaure IDT'' à l’aide de FirstIDT.
    2. Le compteur de l’entrée IDT0[i] vaut 8. On place dans IDT’[i] (donc aussi dans IDT'') notre propre handler de sniffing qui route vers la routine placé dans IDT0[i]. FirstIDT[i] se voit aussi inscrire cette valeur afin d’avoir un état cohérent par la suite si notre handler se faisait écraser. IDT0[i] est mit à 0 ainsi que son compteur.
  5. IDT'' repasse en RO. Flush des caches pour que tout ca soit propre.
  6. On peut penser à ajouter ici une vérification du CR0.WP.
  7. Retour d’exception avec IRET.

Ok normalement tout cela ne fonctionne pas trop mal. Reste un gros problème, l’écriture des handlers qu’on a du hooké, le #PF et le #DB. Ceux-ci doivent rester valident même en cas d’écrasement. On comprend bien que si un module écrase ces entrées en 2 fois (2*4 bytes, le plus courant) elles seront invalide. Souvenez vous plus haut, les bits du pointeur sur le handler sont repartis dans les 2 dwords, en modifier un sans l’autre crée donc un état incohérent. Du fait qu’on a forcément du besoin du #PF et de #DB ou de #BP (cas avec Bee-Lee) il nous trouver une solution pour éviter leur écrasement lors qu’un module veut les hooker.

Une solution simple est de déplacer l’IDT lorsqu’on identifie une écriture sur l’un de ces handlers. Cette IDT sera une copie de IDT'' et ne sera assigné sur le core uniquement pour traiter l’écriture dans un ces handlers. Cela donne le scénario suivant :

  1. Dans notre #PF on détecte une tentative d’écriture sur l’un des handlers critique. On déplace l’IDT sur une copie de IDT''.
  2. L’instruction écrit dans IDT''
  3. On arrive dans notre #DB qui fait la même chose que précédemment en plus de remettre l’IDT du core sur IDT''. Evidemment nos routines de #DB et de #PF sont capables de router les exceptions vers celles du modules lorsqu’elles se font écraser.

Voilà, j’entends les couinements de Babo0n au fond mais ca doit tenir la route. Vous remarquerez que c’est un peu compliqué pour juste sniffer l’IDT et j’aurais peut être du faire un schéma pour que vous visualisiez tout cela mais j’avais plus d’encre :) Une dernière chose à propos des informations que renvoient les routines de sniffing. Il serait intéressant de rapporter des infos sur les registres, la pile, le thread et le processus ainsi qu’une possibilité de filtrage que l’utilisateur détermine, à voir.

Pour finir, je fais donc appel à vous ! Si vous ne comprenez pas des choses ou que certaines ne sont pas clair n’hésitez pas me poser des questions ! Je vais essayer de coder cela dans les prochains jours mais c’est loin d’être simple et je voudrais être sur de ce que je fais. Hé oui parfois faut réfléchir avant de pisser du code …

Au final ca serait un tool sympa pour aider au reverse de certaines protects un peu violente. A noter qu’à l’aide de la virtualisation hardware tout ceci est beaucoup plus simple à réaliser car il suffit de mettre des flags à 1 pour demander à l’hyperviseur de prendre la main lorsqu’une exception arrive !

Enfin quelques lectures et tools qui valent la peine :

Implementing SMM PS/2 Keyboard sniffer

Return-oriented exploiting

New Tool: VMMap v1.0 | Mark speaking at Microsoft TechEd 2009

Understanding the kernel address space on 32-bit Windows Vista

Attacking Intel TXT: paper and slides

Le blog de Kris Kaspersky

Entry Filed under: RE

3 Comments

  • 1. dan  |  février 26th, 2009 at 17:42

    >Sur un core l’IDT est stocké dans le registre IDTR qui peut être accédé avec l’instruction LIDT et écrit à l’aide de SIDT

    c’est pas plutôt l’inverse ?


  • 2. admin  |  février 26th, 2009 at 17:53

    Yo,
    Effectivement, je m’ai trompé, c’est bien l’inverse. Merci de l’avoir vu !
    +


  • 3. Baboon  |  février 26th, 2009 at 20:07

    Pas besoin du #BP handler pour bee-lee patate
    :-P
    Sinon tu as interet a implémenter mon truc pour le controle de la lecture de l’IDT
    ;)

    (Sinon si moi je couine, moi au moins je sais manger un kebab sans me baver dessus :-°)

    Bref il faut que je code la partie permettant de reprendre la main « apres » l’iretd …


Trackback this post


Calendar

mars 2024
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts