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
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).
<?phpif(!empty($_POST['username'])&&!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.';elseecho"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 requête:
$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 = "OR1=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.
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:
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
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.
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.
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.
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:
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:
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.
We are going to see the way of creating the most efficient anti-debugger! Yes it prevents the target system from dynamic analyzis (not static). Thus we cannot set breakpoints anymore, do step by step or start a program from a debugger. You probably wonder how this is possible ?
All notions explained there are essential to make a process filter in an interrupt handler (next article).
If you feel at ease with interrupts, you can go straight at part II.
I need to thank especially Mysterie who gave me a hand during the development of my program. You can have a look at his (french) website. I also thank Alex_I who gave me some advice on freenode.
I/ Interrupt Descriptor Table (IDT) and interrupts
1. Basic notions
An interrupt stop shortly or definitively the normal execution of a program in order to execute another code called interrupt handler (or ISR for Interrupt Service Routine). The ISR address is stored in the IDT. For instance when a program try to write at a bad address, an interrupt handler is started to fix the problem if it is possible.
On 32-bits Intel processors, number 32 to 255 interrupts are defined by the operating system (user defined), they are called maskable interrupts. Non-Maskable Interrupts (NMI) cannot be disabled easily and they are pre-defined. It is those that have the highest priority. On the other hand, maskable interrupts are disabled when IF flag is set to 0.
There are two kind of interrupts: first ones are asynchronous and are called hardware interrupts or external interrupts. They are triggered by an external event (keyboard, serial port etc.). For example if you plug in a peripheral to a computer port, an ISR will be started. There is also synchronous interrupts which are called when an instruction provokes an error (division by zero, illegal memory access). They can be called voluntarily by the program too (explication in next paragraph). This second sort of interrupt is called exception.
An exception can be split in three categories: traps, faults and aborts.
A fault is an exception which does not always end up the program: the problem can be fixed. Once this error fixed, the program restart from the instruction that caused the error. For instance, if the program attempt to access an invalid virtual memory page, the operating system will try to allocate this page and will start again the instruction responsible for the error.
A trap is an exception that, after the interrupt handler execution, will resume the program from the instruction just after the one which caused the exception (wow!). For instance, after a breakpoint (int 3), we have better not to continue the program execution from the instruction which provoked the breakpoint – \xcc – because we would fall in an infinite breakpoints loop.
Aborts are exceptions which interrupts definitively the program (i.g. incorrect addresses in the SSDT).
You will also meet the term of software interrupt. They are exceptions that are called voluntarily by the process via an instruction: system calls are a good example since they are called voluntarily by the program thanks to \x2E instruction (see the previous article about the SSDT, even if syscalls are now called on latest systems by SYSENTER).
The interrupt number 3 (breakpoint) is also a software interrupt: it is called by \xCC instruction.
To call an interrupt handler in assembler, we use « int n » instruction where n is the number of the interrupt handler.
We have to notice that there is only one IDT per microprocessor. Namely if the target computer uses a quad core, we will have to hook the four IDTs! But in this article we will only hook a single core because hooking a lot of cores is unexciting, except it could help scripts kiddies with a beautiful POC.
At last, there are priority levels we call IRQL (Interrupt ReQuest Level). In this way, an interrupt can be triggered in another interrupt only if its IRQL is greater than the one of the current interrupt. The lowest IRQL is called PASSIVE LEVEL. It is the one used by all user-mode processes and a lot of ring0 functions like DriverEntry. IRQLs have been designed in order not to disable all interrupts everytime via cli instruction because some cannot wait. For more information about IRQLs I advise you to click on this link.
To see all addresses in the IDT, WinDBG offers us « !idt -a » command. To disassemble an interrupt handler: « uf Kitrap03 » for example for the number 3.
2. Breakpoints and interrupts used for debugging
Both interesting interrupts in this paper are the traps int 1 and int3. The second, the most famous, allows a debugger to set a software breakpoint. You certainlty wonder how it works.
a) Breakpoints
\xcc opcode calls number 3 ISR. Thus to set breakpoints, debuggers overwrite the instruction chosen by the user – i.e. the place of the program where the user set the breakpoint -, by \xcc opcode, at the start of the program, after having saved the overwritten instruction. More precisely we only save one byte. When the program is started from the debugger, the microprocessor handles each instruction and, when current instruction is \xcc, it switches to the interrupt handler number 3. Once this one is over – that is to say when the user choose to resume the program -, the debugger replaces the \xcc byte by the original byte it had saved before and the program do not suspect anything! Obviously it replaces at once the same byte in memory by \xcc in order to keep the breakpoint.
On the other hand, we have to distinguish software breakpoints we just explained from hardware breakpoints whereby microprocessor gives us a hand: we write an address in the Debug Registers DR0, DR1, DR2 and/or DR3.
In this way, each time the debugged process attempts to read, write or execute on of these addresses, int1 is started and not the ISR number 3. Following paragraphs give more details on this topic.
Notwithstanding, hardware breakpoints have a drawback: they are limited to 4 on x86 processors in opposition to software breakpoints which have no numerical limit except the number of instructions in the debugged process memory.
b) Interrupt 1 and Debug Registers
Int 1 is used in a lot of debug cases in constrast with int3. According to what it must do, int1 behaves either like a trap or like a fault. DR6 and DR7 Debug Registers allow the interrupt to specify what int1 must do. We can also notice that DR4 and DR5 are absolutely useless. Let’s remember that the purpose of DR0, DR1, DR2 and DR3 is to store addresses for hardware breakpoints.
Here is an extract from volume 3A in Intel manual which illustrates the tasks that int1 can achieve:
These tasks are based on the value of DR6 and DR7 bits:
To sum up, int 1 is used for step by step, hardware breakpoints, problems linked to Debug Registers access and stack breakpoints.
I will not extend to all of these points because they are well documented in Intel manual. However I am going to explain step by step mode: when TF flag – contained in EFLAGS register – is set to 1, that means that step by step is enable. So at the end of each instruction – except the one which caused the TF modification – int 1 is triggered. We can in this way have a look at all registers and at the stack after each instruction from a debugger.
sidt instruction returns the following IDTINFO structure:
typedef struct {
WORD IDTLimit;
WORD LowIDTbase;
WORD HiIDTbase;
} IDTINFO;
LowIDTbase corresponds to the two lowest bytes of the IDT address.
HiIDTbase corresponds to the two highest bytes of the IDT address.
If we add IDTLimit to the IDT address, we have the address of the last byte of the IDT.
To get the full address (4 bytes) of the IDT (LowIDTbase + HiIDTbase) we will use the next macro:
“a” argument corresponds to the lowest bytes and “b” to the highest bytes of the ISR address. Let’s take a = 0×1234 and b = 0xFFFF, we have to obtain 0xFFFF1234 address. b variable is casted on 4 bytes then is shifted to 16 bits to the left, therefore 0xFFFF becomes 0xFFFF0000. In binary: 11111111 11111111 is shifted to the left after a 32 bits cast so it becomes 11111111 11111111 00000000 00000000.
We now apply an OR filter with the lowest bytes. In binary, 0×1234 = 00010010 00110100. We make a DWORD cast to get the equivalent on 32 bits, so 0×1234 becomes 00000000 00000000 00010010 00110100.
With OR filter,
00000000 00000000 00010010 00110100
OR 11111111 11111111 00000000 00000000
= 11111111 11111111 00010010 00110100
that corresponds to 0xFFFF1234.
We have now the IDT address.
2. ISR address overwriting
The IDT is an array of structures declared like this:
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;
To get the interrupt handler address we will implement MAKELONG macro on LowOffset and HiOffset as before.
To modify the ISR address we will also alter LowOffset and HiOffset. I designed MAKELOW and MAKEHIGH macros which are the reciprocal of MAKELONG macro. If you have well understood the theorical working of this MAKELONG, you will not meet any problem with the others that is why I send you to read the program source code to see how they work.
We can now hook an ISR in the IDT so we have to know what an ISR looks like in order to develop our hook function. Indeed a void type function would crash the OS.
III/ Running of an ISR and its inner structure
1. Running of an ISR
Let’s look at what happens when an interrupt is called.
You should have some notions about protected-mode memory pagination so as to understand the rest of the article.
Let’s remember quickly what is a segment and a segment selector. The scheme below split the segment selector bits in three parts. INDEX corresponds to the index of a descriptor in the GDT or in the LDT. If TI is set to 1, it is a LDT index, if TI is 0, it is a GDT index. The GDT and the LDT are both arrays of segment descriptors. RPL corresponds to the privilege level of the segment. This level goes from 0 to 3 and corresponds to the ring level. CS (Code Segment) register contains the selector of the current text segment. Thus CS points to the segment which handles the program instructions and EIP is the offset of the next instruction to execute in this segment. CS:EIP is useful to point out a memory address. The two lowest bytes of CS are not called the RPL but the CPL (Current Privilege Level), i.e. the privilege of the current executed code. There are also DS (Data Segment) register for the data segment selector and SS (Stack Segment) for the stack segment selector and so forth.
The structure of a segment selector (CPL instead RPL for CS):
Entries in the IDT are called Descriptor Gates. Indeed they supply a lot of information to access an ISR like obviously its address (segment selector + offset) or even the required privileges. We distinguish the Interrupt Gates from the Trap Gates and from the Task Gates, all these ones being possible cases of Descriptor Gate. My program uses IDTENTRY structure to handle them. We have to notice that Task Gates are not used on Windows except for the number 2 and 8 interrupts which are very close to the hardware that is why I chose not to introduce them here because I should define far too many new notions like taks and TSS that are a little off-board compared to our main thread which is to create an anti-debugger.
If the microprocessor deals with an Interrupt Gate or a Trap Gate, there are two possibilities: DPL corresponds to the required privilege level to execute the ISR; it is supplied by the Descriptor Gate. If the DPL and the CPL are different, namely if the original program and the interrupt have a different privilege level, un stack changement occurs. This check is real only if the interrupts are started from the INT, INT3 or INTO instructions.
On the other hand if the CPL is the same than the DPL, the program stack is shared.
Same stack
EFLAGS, CS and EIP registers are “pushed” on the stack in this order.
Some exceptions add an error code onto the stack. Here is its structure according to the Intel manual:
Thanks to this error code the interrupt can determine the reason of its being and the place where it has been triggered in the case where there is a specifical link with a memory segment or another interrupt. When EXT bit is set to 1, this means that the error provides from a place outside the program like another interrupt. When IDT is enable, Segment Selector Index points to an IDT entry, i.e. the interrupt has been triggered from another interrupt. In the other case it is the index of a segment located in the GDT or in the LDT; if TI is set to 0, it is the GDT else it is the LDT. If an interrupt is called from the int instruction, no error code will be push whatever the exception is.
The text segment selector – located in CS register – is updated and so EIP is. Indeed the new CS is supplied by the Descriptor Date like the ISR offset. Thus the next instruction to execut (namely the first one of the ISR) is pointed by the pair CS:EIP.
If the Descriptor Gate of the ISR is an Interrupt Gate, IF flag is set to 0, i.e. it disable maskable interrupts.
Then the interrupt handler is executed.
Different stack
SS, ESP, EFLAGS, CS, EIP are saved internally.
TSS (Task State Segment) includes a pointer to a stack for each privilege level (0, 3). The selector segment and the stack pointer are thus loaded from the current process TSS. The selector of the TSS segment is located in TR (Task Register).
SS, ESP, EFLAGS, CS and EIP are pushed onto the stack in this order.
Then it is like for the same stack (error code, update of CS and EIP, IF, then interrupt handler execution).
2. ISR skeleton
Therefore RET or RETF instructions do not match to leave an ISR because there is no EFLAGS pop. I remind you that RET pops only the value of EIP and RETF the value of EIP + CS for a far call, i.e. when there is a code segment changement.
We have so to use IRET (16 bits) or rather its 32 bits equivalent which is IRETD, to leave an ISR properly. In this way EFLAGS is poped and recovers its value.
At last we must not use a void function for the hook. Indeed the ISR would look like this:
Great a useless prologue! We could add something like MOV ESP,EBP
POP EBP
or its equivalent that is LEAVE just before IRETD so that there is no problem in the stack with the EPB push. But it is better to use a naked function with __declspec(naked) type; so the ISR becomes:
kd> uf f7c44490
IDTHOOK+0×490:
f7c44490 cf iretd
Bingo no crash! No more useless EBP.
In the next article we will see a technical to filter the processes from an ISR. See you soon.
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:
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.
Bonjour,
Cela fait quelques temps que je n’ai pas posté d’articles sur ce blog en raison des vacances d’été. Je travaillais également sur les algorithmes de tri, de récursivité, de tree/graph traversal.
Mais maintenant me voici et j’ai choisis les hooks userland comme sujet de cet article ainsi que les injections de DLL. Certes 0vercl0k avait lancé quelques articles là dessus mais il ne traitait pas toutes les techniques possibles.
Je vais donc (presque) toutes les détailler pas à pas. Le hook de l’Import Address Table (IAT) sera la première expliquée puis nous parlerons de l’Inline Function Hook pour transiter sur les trois types d’injections de DLL, à savoir l’utilisation de SetWindowsHookEx(), la Remote Thread et enfin la modification de la base de registres.
Tout le long de cet article, notre but sera de hooker la fonction MessageBoxA de notre programme cible. Il s’agit d’un crackme basique codé par Xylitol: on a deux MessageBoxA, la première avec une zone de saisie de texte et la deuxième correspondant au message d’erreur.
Les Hooks Userland
Le Hook de l’IAT (Import Address Table)
Vous trouverez le code source de la DLL et de l’injecteur décrit dans ce paragraphe à la fin de l’article.
Si la définition explicite de l’IAT vous paraît floue, je vous recommande alors de bien vous imprégner des concepts du format PE.
Vous considérez maintenant les rouages du format PE comme acquis: la section des imports est un tableau de structures IMAGE_IMPORT_DESCRIPTOR. Pour chaque DLL importée, il y a une IMAGE_IMPORT_DESCRIPTOR. Cette structure contient les pointeurs OriginalFirstThunk et FirstThunk qui pointent chacun vers un tableau d’IMAGE_THUNK_DATA, plus communément appelés respectivement l’Import Name Table et l’Import Address Table. La DLL chargée de hooker l’IAT est composée d’une boucle principale qui visite chaque DLL importée en incrémentant hImportDescriptor de la taille d’une structure IMAGE_IMPORT_DESCRIPTOR – pointeur initialement sur la section des imports.
Dans cette boucle subsiste une autre boucle qui elle, visite chaque fonction de la DLL.
Pour obtenir le nom de la fonction, on récupère la Virtual Address (VA) renvoyée par OriginalFirstThunk.u1.AddressOfData – laquelle il faudra rajouter l’image base (hImageBase) pour obtenir la Relative Virtual Address (RVA) – de l’IMAGE_IMPORT_BY_NAME de la fonction, qui elle possède un membre Name, lui qui contiendra le nom ASCII de la fonction.
Si le nom de cette fonction est MessageBoxA – autrement dit notre fonction à hook – on renvoie l’adresse de son IAT (&(hImportAddressTable->u1.Function)) que l’on modifiera ensuite par l’adresse de notre fonction de hook dans HookIAT().
Bien entendu, MessageBoxA n’est qu’un exemple de fonction à hook et l’on pourra modifier d’autres fonctions comme par exemple NtDeviceIoControlFile() afin de masquer les connexions d’un programme malicieux (voir un article antérieur).
La DLL est maintenant prête à être injectée dans un processus cible; nous détaillerons trois techniques différentes pour parvenir à cela dans la deuxième partie (Injection de DLL).
L’Inline Function Hook
Il s’agit d’une technique difficile (ou du moins pas très envoutante) à mettre en place mais bien plus puissante que l’IAT Hook.
Voilà comment cela fonctionne globalement:
Il faut écraser le préambule de la fonction à hook (les toutes premières instructions) en y mettant un jump vers notre fonction de hook – appelée également fonction de détour. Il faut bien sûr sauvegarder ce préambule – l’emplacement de sauvegarde s’appelle le trampoline – pour rappeler la fonction originale depuis la fonction de hook.
Voici à quoi ressemble un préambule avant XP SP2:
push ebp
mov ebp, esp
Après XP SP2 (et sur les vista / seven):
mov edi, edi
push ebp
mov ebp, esp
Pour le savoir il suffit juste d’ouvrir kernel32.dll par exemple avec ollydbg et de regarder les trois premières instructions de n’importe quelle fonction.
Le jump + l’adresse de la fonction de hook font 5 octets, ce qui correspond à la taille du préambule dans les nouveaux OS Windows (on dirait qu’ils nous facilitent la tâche).
Pour résumer, la fonction originale est appelée et le jump qui remplace le préambule nous amène à notre fonction de détour. Ensuite cette dernière appelle le trampoline puis jump sur la fonction originale + 5 octets. Une fois la fonction originale terminée, nous somme de retour dans notre fonction de détour et là on peut traiter les résultats obtenus. Enfin on revient à l’instruction qui suit celle qui avait appelée la fonction cible.
Voilà deux schémas pour vous aider à mieux comprendre:
Fonctionnement normal
Hook en place
Je n’ai pas choisis de coder le Proof Of Concept de cette technique par manque de temps mais ce sera peut-être le fruit d’un futur article (ou pas).
Les Injections de DLL
SetWindowsHookEx
Cette technique est assez stable mais elle a l’inconvénient de cibler tous les processus actifs sur le système. En effet, SetWindowsHookEx() est la fonction utilisée par la plupart des keyloggers UserLand. Le fonctionnement est simple: le premier argument définit le type de hook utlisé comme le clavier ou la souris. Ainsi la première fois que l’utilisateur presse une touche sur le processus cible, la DLL de hook est chargée en mémoire. Puis à chaque nouvelle touche pressée, c’est la fonction passée en second argument qui est appelée.
L’intérêt ici est de charger en mémoire la DLL de l’IAT précédemment compilée lorsque la DLL de hook du clavier est chargée dans le processus cible. Ainsi de la même façon, la fonction MessageBoxA sera détournée.
Plus concrètement ici, l’utilisateur lance le programme cible et une zone de saisie de texte apparaît. Lorsque l’utilisateur écrit dans cette zone de saisie, la DLL d’IAT Hook est chargée en mémoire, ainsi après avoir cliqué sur OK la prochaine MessageBoxA est compromise.
Voici un screenshot pour illustrer:
L’injection par Remote Thread
L’injection de DLL via un Remote Thread est la plus répandue.
Le déroulement est assez simple: l’injecteur ouvre le processus cible via OpenProcess. Ensuite il alloue de la mémoire dans le processus moyennant VirtualAllocEx pour ensuite y écrire le chemin absolu de la DLL de hook – celle que l’on a appris à construire dans la première partie sur le hook de l’IAT. Enfin la fonction CreateRemoteThread va nous permettre d’exécuter la fonction LoadLibraryA – dont on aura récupéré l’adresse avec GetProcAddress – à distante en créant un nouveau thread. On lui passera en argument le chemin absolu de notre DLL qui figure dans la mémoire alloué précédemment.
Cette technique est très efficace mais l’injection n’est pas permanente: si le programme cible est redémarré, il faudra relancer l’injecteur pour le corrompre.
La base de registre
Dans la base de registre, HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs spécifie toutes les DLL que doit charger USER32.dll en mémoire. De plus USER32.dll est chargé par quasiment tous les processus (#include <windows.h> le fait).
En revanche plusieurs restrictions s’ajoutent: il faut rebooter la machine pour que les changements prennent effet et cette technique marche seulement sur les Windows NT, 2000 et XP.
Sur vista et seven ce méchanisme est désactivé.
Mon site web est désormais disponible à l’adresse www.shp-box.fr.
Bien que le précédent nom de domaine sera certainement conservé, pour une durée non définie, je vous prie de mettre tout de même à jour vos lecteurs rss.
Qu’est-ce qu’un loader? C’est un programme qui à première vue semble inoffensif. Or celui-ci, une fois lancé, extrait un malware de lui-même pour le copier sur le disque dur de la victime et le lancer. Le logiciel malveillant est en fait bindé au loader. Nous allons voir l’allure que peut avoir un tel programme au niveau du code (1) et de quelle manière on peut insérer le rootkit dans celui-ci.
Dans un premier temps, il nous faut développer notre rootkit qui est ici un driver puisqu’il s’exécute en ring 0 (kerneland). On va effectuer un simple DbgPrint() afin d’avoir la preuve que le rootkit sera bien lancé via DbgView. Si ceci ne vous semble pas clair, je vous invite à relire mon edito qui énumère les notions requises à avoir pour bien comprendre tous les détails de cet article. Le code source du driver se trouve à la fin de cet article (2).
Nous allons ensuite chiffrer le fichier .sys précédemment obtenu lors de la compilation en xor afin d’être plus discret vis-à-vis des antivirus (cela chiffre les chaînes de caractères par exemple). Petit rappel sur ce chiffrement: on récupère successivement un à un chaque octet du fichier que l’on va confronter à un ou exclusif (xor en anglais, c’est à dire l’un ou l’autre mais pas les deux) avec chaque octet de la clé (qui est une chaîne de caractère). Bien sûr la clé étant beaucoup plus petit que la longueur du fichier, il faut l’auto-concaténer pour obtenir une longueur identique. Voici une petite table de vérité pour que cela paraisse plus clair:
Le code permettant de chiffrer le fichier se trouve à la fin de cet article (3). J’ai utilisé la clé “seraphicsquad” pour chiffrer le driver.
Il faut maintenant insérer le fichier obtenu dans le loader (1). Windows permet la manipulation des ressources. Une ressource est en fait un fichier (exécutable, image, son, librairie) que l’on va insérer dans un exécutable, ici la ressource sera donc notre driver chiffré.
Pour injecter notre ressource, il existe un fabuleux outil que tout le monde connait du nom de Resource Hacker. Voyons un petit screenshot que nous allons détailler.
Vous ouvrez donc le loader.exe avec Resource Hacker, puis “action > Add a new Resource > Open File with new resource…”. Vous mettez ensuite dans “Resource Type” la chaîne “BINARY” (à retenir pour le code) et dans “Resource Name” vous écrivez “ROOTKIT” (à mémoriser pour le code). Dans “Resource Language” j’ai mis “C” même si je pense que cela est sans importance. On aurait pu choisir d’autres valeurs mais c’est celles-ci que nous utilisons dans le code du loader.
Vous avez maintenant bindé votre rootkit au loader! Santé!
L’étape suivante va être l’extraction des données au sein du loader pour ensuite les écrire dans un endroit du disque. Les fonctions de l’API Windows à utiliser sont donc FindResource() puis LoadResource() et enfin LockResource() qui récupère l’adresse de la ressource (du rootkit). La fonction SizeofResource() permet d’acquérir la taille de la ressource. On copie ainsi chaque octet de la ressource dans un fichier du disque (\Rootkit.sys) sans oublier avant de déchiffrer notre ressource qui a été auparavant xor-chiffré avec la clé “seraphicsquad” (l’algorithme de déchiffrement est strictement le même que celui de chiffrement si vous réfléchissez bien avec la table de vérité affichée ci-dessus).
La dernière et ultime étape consiste à lancer le driver. Pour cela il existe deux méthodes:
La première (déconseillée) consiste à utiliser la fonction ZwSetSystemInformation() exportée dans la librairie ntdll.dll. Elle a pour avantage de créer aucune clé dans la base de registre mais elle peut amener à des BSODs (Blue Screen of Deaths) mais je ne m’étendrai pas sur celle-là.
La deuxième (recommandée) consiste à faire appel au SC Manager mais elle provoque la création de clés dans la base de registre. Mais bien évidemment, une fois votre rootkit lancé, vous pouvez les masquer en hookant les fonctions nécessaires (peut-être l’objet d’un futur article). Vous aurez alors besoin pour lancer le driver de cette manière des fonctions CreateService(), OpenService() ou encore StartService(). Si vous voulez décharger le driver, la fonction DeleteService() est votre amie <3. Pour plus de détails, consultez le site de mon ami Ryscrow.
Avant de lancer le loader ouvrez DbgView pour vous assurer que le rootkit est bien lancé. Maintenant place aux codes sources avec avant tout un petit screenshot.
J’ai décidé à présent d’orienter mes articles dans une seule direction principale et non de m’éparpiller dans tous les domaines, bien que je continue à faire beaucoup de web et de reverse engineering sous linux (utumno, maze etc…) afin d’améliorer mon efficacité lors des CTFs (Capture The Flags).
Cet article figure ainsi le premier volet d’une série d’articles qui portera sur l’art des rootkits sous Windows que j’appellerai plus communément le rootkiting.
Pour vous lancer dans ce monde vaste et passionnant, vous aurez besoin de quelques notions de programmation système orienté Windows (C, certainement l’assembleur):
Lancement d’une machine virtuelle (j’utilise virtualbox) avec un mode de débogage pour pouvoir déboguer le système depuis WinDbg installé sur la machine hôte. Je vous recommande le tutoriel (anglais) sur le site de virtualkd.
Communication ring0 – ring3 via les IRPs: extrait du livre Subverting the Windows Kernel (anglais).
Vous serez cependant apte à comprendre globalement les articles qui vont suivre sans ces notions mais lire les tutoriels cités ci-dessus vous permettra de rentrer plus en détail dans les mécanismes.
Cela fait quelques temps que je n’ai pas posté d’article sur ce blog (accident de jokari) alors je ne vais pas perdre une seule seconde pour m’y atteler.
Aujourd’hui nous parlerons web et plus précisément de LFI (Local File Include). Je vais ainsi vous détailler une technique d’exploitation assez récente et peu connue qui consiste à utiliser les filtres php. Nous verrons dans un premier temps quelques explications sortant directement de la documentation php sur le sujet puis nous regarderons de plus près ce que nous pouvons en faire dans le cas d’une faille include.
Je cite:
PHP dispose de nombreux protocoles natifs, pour différents styles d’URL, à utiliser avec des fonctions de fichiers telles que fopen() et copy().
Ce sont les flux d’entrée et de sortie qui nous intéressent particulièrement ici. Je vous conseille de lire la documentation (1) avant de poursuivre la suite.
Pour résumer rapidement, donnons un petit exemple: si vous lancez un fopen() sur php://output dans votre script php, toutes les données que vous écrirez serons écrites dans le STDOUT; il s’agit donc d’un équivalent aux fonctions echo ou print.
Certes php est puissant, mais depuis sa version 5.0, php nous a sorti une sorte de cadeau empoisonné. php://filter permet en effet d’appliquer un filtre aux données qui transitent dans les flux d’entrée et de sortie. On peut illustrer cela avec la fonction readfile(): au lieu de récupérer les données d’un fichier en clair, vous pouvez récupérer les données encodées en base64, en rot13, ou encore (dé)compresser en bzip2 le fichier et j’en passe. Parallèlement, la fonction include() et ses dérivées supportent très bien les filtres … Nous allons par conséquence voir maintenant un cas de figure d’exploitation d’une LFI moyennant les filtres php.
Cette technique d’exploitation ne marche pas sur toutes les LFI. Par exemple, impossible de mettre en place un php://filter dans la ligne suivante: include("./".$_GET['page']);
Cependant, la ligne suivante est “pownable”: include($_GET['page']);
Mais alors, me direz-vous, pourquoi ne pas exploiter cela en RFI (Remote File Include). Malheureusement, cela ne suffit pas à exploiter une RFI. Effectivement, si dans le php.ini, le “allow_url_include” est à 0, vous aurez un joli message dans ce genre:
Warning: include() [function.include]: http:// wrapper is disabled in the server configuration by allow_url_include=0 in /var/www/index.php on line 83
Dernière technique d’exploitation possible: la LFI. Votre premier réflexe est d’inclure alors access.log et là vous tombez sur une jolie erreur php: quelqu’un est déjà passé par là et vous a laissé le access.log avec un jolie bug php en incluant par exemple
“<?php fopen(“. Vous persévérez, et il se trouve qu’il n’y a pas de sessions sur ce serveur ou qu’elles ont été déplacées de /tmp. Il vous reste nos fameux filtres php!
Grâce à eux (ou plutôt à cause d’eux), vous pouvez lire le code php de n’importe quel fichier du serveur dont le “config.php”. Vous récupérez ensuite le mot de passe de la base de donnée et vous avez plié l’épreuve du ctf.
Pour faire cela, rien de bien compliqué; vous passez en argument de la fonction include() par exemple: