Quel processeur bizarre - faire des misères au x86... et un peu de PE [Portable Executable]
Bienvenue... et surtout, dites-moi si je vais trop vite
Je vais parler des opcodes, et un peu du format PE et de leurs bizarreries
Je fais du reverse depuis quelque temps. J'ai créé un projet appelé Corkami.
Dans le passé, j'ai participé à Mame, l'émulateur Arcade, et professionnellement, analyste de virus,
mais je suis ici à titre personnel, ce sont mes propres expériences à la maison.
Donc, je viens de mentionner Corkami. C'est le nom du projet de mon projet de reverse [rétro-ingénierie]
Je n'y parle que de choses techniques, pas de pubs, pas de login requis
Directement le pur jus.
J'essaie de le maintenir à jour, et util. J'ai créé des fiches pense-bêtes et d'autre documents
que j'utilise moi-même au boulot, au quotidien
mais c'est juste un loisir. je commence quand les enfants sont couchés
tard dans la nuit, donc ça n'a probablement pas le polish professionnel
que j'aimerais qu'il ait.
Actuellement, Corkami est un wiki et des pense-bêtes
et je me focalise sur la création de preuves de concepts [démonstration d'hypothèse, "proof of concept] [Tiens, salut Bob!]
donc les binaires sont fait-main, d'habitude je n'utilise pas de compilateurs, je créé la structure du PE à la main
pour que ce soit focalisé sur le point pertinent
et qu'on est pas de parasites. on n'a probablement pas besoin d'IDA
pour comprendre ce qui se passe
car je me concentre uniquement sur ce qui est important.
Les binaires sont directement téléchargeables pour que vous puissiez
tester avec votre débogueur, vos outils, vos connaissances,
directement.
pour l'instant, je me suis concentré sur le PDF, l'assembleur et le format PE
quelques autres trucs, mais ce sont les sujets les plus approfondis
du site, et je partage tout ça avec une licence
très permissive, BSD, donc vous pouvez réutiliser commercialement
Même les images sont en format ouvert.
Donc, la raison de cette présentation... il y a quelque temps,
J'étais jeune et innocent, et je pensais que les CPUs, fait de transistors,
était parfaitement logique
et ensuite, un virus m'a piégé... simplement,
IDA était rendu inutile. Donc j'ai décidé de repartir à zéro
et d'étudier l'assembleur et le PE du début.
j'ai écrit en chemin des documents, partagés sur Corkami
et maintenant je vous présente le résultat plus ou moins final,
ou du moins quelques buts atteints. Si j'étais juste un gars qui a étudié l'assembleur
je ne serais probablement pas en train de présenter ici,
à moins d'avoir eu quelques résultats avec certains outils.
Donc, pour résumer, j'ai trouvés des bogues dans tous les désassembleurs que j'ai essayé
et j'ai aussi obtenus quelques plantages. J'insiste que tous les auteurs ont été contactés
et que la plupart des problèmes ont déjà été corrigé,
mais en tout cas, en version 6.1, il plante directement, mais dans la version 6.2
c'est corrigé.
et la dernière version de Hiew [Hacker's view] (enfin, pas la dernière publique)
corrige ça aussi.
Donc, pour cette présentation, je commencerais facile,
je pars du principe que vous êtes tous habitués à l'assembleur ?
Oui. et vous êtes tous habitués, ou
vous avez déjà rencontré des cas d'opcodes non documentés?
Genre vous faites confiance à IDA, point barre.
Est-ce habituel de voir quelque chose non géré par IDA ?
Levez les bras... ok, pas tant que ça.
Bon, après l'introduction en accéléré,
Je parlerais de quelques techniques, puis présenterai CoST, mon programme
et je parlerais aussi un peu du format PE
Bon, vous connaissez tous l'assembleur, on va survoler ...
Donc, on compile un binaire, il y a de l'assembleur, il y a
un rapport, des points communs entre le code source et l'assembleur généré...
et bien sûr, une relation entre langage machine et les opcodes
ce qui est important est que l'assembleur est généré par le compilateur, mais ce qui reste
dans le binaire, sont uniquement les opcodes, qui sont interprétés
directement par le processeur, ce qui implique qu'il sache quoi faire avec,
il s'en fiche si vous ou vos outils
ne savent pas ce qui va arriver. il le fait, tout simplement.
Et le problème, c'est que d'habitude, on ne lit pas les opcodes directement, mais le désassemblage
donc si le désassemblage échoue, on est bloqué
on est aveugle, on ne sait pas ce qui va s'exécuter
l'autre problème est comme les instructions sont de longueurs variables
on ne sait pas ou la suivante commence
donc même la suite est inconnue.
Donc, on créé une seule instruction non documentée dans un programme simple
on utilise le mot-clef 'emit' -- c'est Visual Studio 2010 ultimate --
et on obtient un octet non identifié au désassemblage
on a des juste points d'interrogations.
et donc, même si Visual Studio coûte plusieurs milliers d'euros
il ne sait pas ce qui va se produire....
et si on regarde les documentations Intel
circulez, il y a rien a voir... l'opcode D6... inconnu au bataillon
Microsoft n'a rien à dire, Intel non plus,
donc d'habitude, si vous essayez des choses comme ça, attendez-vous au pire.
Donc, aucune information... en général, ça annonce un plantage...
mais dans ce cas particulier, aucun problème...
nous ne savons pas ce qui se passe, si on regarde les docs Intel et Microsoft, on n'en sait pas plus.
Mais le CPU a juste fait son boulot dans son coin. Ce qui s'est passé est qu'en fait
D6 est un opcode très simple, qui ne fait pas grand chose, mais pas documenté par Intel,
par contre, il l'est par AMD, et la plupart le sont aussi également, par AMD mais pas Intel
aucune idée pourquoi. si quelqu'un le sait...
c'est un opcode pourtant trivial, pourtant... 'y a rien à voir, continuez votre chemin'
Il est utilisé souvent... l'utilisation habituelle de tels opcodes sont les virus et les packeurs
pour éviter les analyses automatisées où trop facile.
Ce qui est ironique, c'est que si vous regardez la doc, elle est pleine de trous. mais le désassembleur d'Intel,
Xed, qui est gratuit mais pas ouvert, gère tous ces opcodes sans problème...
Alors que Microsoft, Visual Studio, et WinDBG, suivent la documentation aveuglément
Donc vous obtiendrez des points d'interrogation alors qu'Intel sait très bien ce qui se passe
'faites ce que je fais, pas ce que je dit'
donc, bien sûr, vous me direz que WinDbg est fait uniquement ce qui est généré
par les compilateurs Microsoft, mais ça exclut WinDBG comme outil d'analyse de virus:
vous rajoutez D6, trivial, et WinDbg est muet.
pas génial
un autre problème est que toutes ses choses non documentées
sont peut-être présentes, l'une dans un virus,
l'autre dans un packeur, etc... donc il n'est pas facile
de trouver un bon regroupement de tels fichiers pour rassembler toutes ces informations
donc par exemple,
quelqu'un vous parle d'une astuce
et vous dit qu'elle est enfouit dans MebRoot (un virus costaud)
donc vous êtes obligé de creuser pour voir juste la partie qui vous intéresse
et en plus, on sait que c'est un virus, donc ca ne circule pas facilement,
et il y a plein de trucs dedans qui n'ont rien à voir dedans,
avant ou après ce qui vous intéresse. Donc c'est le fossé que je veux remplir en fournissant
un groupe de fichier à la fois simple et complet, et focalisé.
Donc, on commence. enfin, du vrai, quelques opcodes non documentés.
Mais avant d'avoir commencé à étudier, je me suis demandé quelles étaient les vrais capacités
des processeurs, quelles instructions existent vraiment.
C'est un peu comme l'anglais: la plupart de la population mondiale comprend ces mots, et
si vous avez déjà désassemblé quelque chose, vous êtes habitués à ces instructions
tous les compilateurs les utilisent. elles sont tellement répandues que si elles sont absentes,
on se sent pris au dépourvu.
mais les processeurs Intels datent des années 70. Donc,
comme l'anglais de Shakespeare, vous voyez que c'est de l'anglais... mais, ça veut dire quoi en fait ?
d'ailleurs, j'ai déjà oublié
ces instructions sont toutes gérées par tous nos processeurs, pourtant, on n'a plus l'habitude de les lire
c'est plus que génant
En fait, j'ai écrit un exemple qui n'utilisent que ces vieilles instructions, qui font vraiment quelque chose
donc si vous vous sentez ici comme chez vous, je voudrais vous demander 'quel âge avez-vous ?'
parce que même moi, j'ai l'habitude de PUSH/JUMP/CALLs, mais ça, euhh...
pourtant, ça marche même sur un i7, et c'est utilisable par les virus,
les packeurs, etc... pourtant, la plupart est complètement inutilisée de nos jours, bien que parfaitement
gérées par nos processeurs modernes.
Et, tel l'anglais, c'est une langue qui évolue, et les générations précédentes
n'ont pas l'habitude des derniers mots à la mode.
Ces instructions sont parfois présentes sur les processeurs les plus récent. en une seule instruction, on fait
un CRC32, on décrypte l'AES, on compare des chaînes, ou d'autres opérations complexes.
Donc, c'est possible sur un processeur moderne. pas tous, bien sûr.
une que j'aime bien est MOVBE -- move big endian -- parce que c'est le vilain petit canard du lot...
uniquement implémentée sur les processeurs Atom, donc comme ce netbook, qui la gère...
alors qu'un i7 64-bits ne l'a pas, même si lui aura CRC32, peut-être AES, etc...
donc, tant pis pour la retro-compatibilité
à ma connaissance, il n'y existe aucun processeur qui gère à la fois CRC32 et MOVBE.
De plus, MOVBE est un peu inutile car il fait la même chose que MOV et BSWAP réunit...
donc, bizarre. en tout cas, ce mini-PC a une instruction que la plupart des PC n'ont pas
Pourquoi ? quelqu'un sait?
[Auditeur:] "cette instruction est documentée ?"
oui
totalement documentée officiellement
[Auditeur:] "Mais, c'est un drapeau du processeur juste pour cette instruction, ou implicitement lié au
processeurs Atom ?
mmm, je ne sais pas. je vérifie la valeur par CPUID, mais j'ai oublié la définition exacte.
il y a tant d'informations données par CPUID que j'ai vite oublié.
Autre chose, spécifique à Windows, car je m'intéresse aux virus...
avant d'exécuter quoi que ce soit, je me demandais quelle était la valeur initiale de chaque registre
quand le programme démarre.
et ça vous donne en fait de l'information fiable, qu'on retrouve utilisée par les virus
donc par exemple, au démarrage, EAX vous dit si vous êtes sur un OS ancien (XP ou avant)
ou plus récent, Vista ou 7.
de même, ce n'est pas utilisé par les virus à mon souvenir, mais si GS est nul, on est dans un OS 32 bits
sinon, sur un 64b.
J'utilise ça régulièrement, on le verra plus tard.
De même, la relation entre les registres n'est pas forcément évidente. il y en a beaucoup,
et j'ai été surpris qu'une instruction à virgule flottante (FPU) change le statut (FST), les registres
eux-mêmes (STx), les registres MMx aussi, mais en plus, toutes les documentations en ligne associent
ST0 et MM0, ce qui semble logique, alors qu'en fait, une seule instruction modifie
non pas MM0, mais MM7, dans l'autre sens.
donc après une instruction comme "load PI" [FLDPI] on regarde la valeur de MM7,
ça peut-être utilisé comme astuce simple.
alors que toutes les documentations, Wikipedia ou autre, se trompent.
quelque chose d'autre qui peut être utilisé comme anti-débogueur sous XP: le FPU modifie aussi CR0
donc ça fait une astuce anti-émulateur plutôt inattendue rien qu'avec le FPU.
'store machine status word' [SMSW] est une instruction qui date du 286, quand le mode protégé était
récent et incomplet, qui permet de lire les valeurs de CR0, même depuis le mode utilisateur.
alors que 'MOV CR0' est privilégiée
pour une raison inconnue, le mot de poid fort est officiellement non défini.
donc à priori, on ne sait pas ce qu'il va contenir,
mais en fait il s'agit bien du même mot de poid fort que CR0.
et, sous XP, après une instruction FPU, la valeur de CR0 est modifiée, mais reprend sa valeur initiale
toute seule par la suite. donc juste avec SMSW, verifier le résultat, puis faire une opération FPU,
alors SMSW donnera un résultat différent, mais ensuite, le résultat redeviendra comme à l'origine.
un moyen élégant de faire des boucles en apparence infinies. une astuce complexe anti-émulateurs.
Une astuce similaire sous Windows 32b, où GS n'est pas stocké dans le CONTEXT, donc quand l'OS passe d'un
fil d'exécution à l'autre (thread-switch), GS est remis à zero. Donc, si on attend, quoi qu'on fasse,
GS change comme par enchantement. Mais si vous faites du pas à pas, c'est lent, donc l'OS change de fil,
et GS est perdu dès l'instruction suivante.
De même, si on attend que GS devienne nul, on finira par sortir de la boucle.
En tout cas, la première fois, ça défiait mon entendement... sans autres process ou autre, je ne comprenais
pas d'où ça venait. Au moins, mon exemple commence dès le début avec ça
c'est plus pratique
une autre particularité, c'est que ça met du temps pour être remis à zéro. en faisant 2 boucles d'attentes,
et mesurant le temps entre les 2 (changement de fil), ça prend somme toute assez longtemps, comparé à une
exécution standard. ça fait un anti-émulation solide.
Je pensais tout naturellement que NOP était parfait! NOP, c'est NOP, ça ne fait rien, donc, aucun problème !
Mais à l'origine, NOP est 'échange ?AX avec lui-même' (xchg ?ax, ?ax) mais c'est le cas pour le NOP encodé 0x90
mais il y a un autre encodage de XCHG EAX, EAX, qui ne fait rien non plus en 32b
mais, à l'instar de toutes les instructions sur 32b en mode 64b
cette instruction remet à zéro le double mot de poid fort
donc on a un XCHG EAX [,EAX] qui fait quelque chose.
bien qu'initialement il semblerait ne rien faire, de sa relation avec NOP
mais heureusement, le NOP 0x90, lui, ne fait toujours rien, un vrai fainéant ;)
Celui-ci est maintenant bien répandu,
le HINT NOP est un NOP sur plusieurs octets
qui indique au processeur ce qui va être exécuté ou accédé ensuite
quelque soit l'adresse dans un HINT NOP en mémoire,
aucune exception n'est déclenchée
mais ...
autre chose, il est partiellement non documenté par Intel
(comme d'habitude, ce n'est pas le cas avec AMD)
en outre, comme c'est une instruction sur plusieurs octets, avec des opérandes immédiates
si on met ces octets à la fin d'une page mémoire
alors le processeur va vouloir lire l'encodage des opérandes,
et ça va déclencher une exception
donc c'est un NOP qui peut déclencher une exception en bas de page
Merci, Intel
MOV également, je croyais...
qu'il devrait être parfaitement logique
mais, même s'il est documenté, il y a des cas compliqués,
qui n'étaient pas parfaitement gérés dans tous les assembleurs que j'ai essayé
sauf peut-être Xed
on ne peut pas faire un MOV de ou vers CR0 en mémoire,
la documentation dit que le Mod/RM est ignoré
ce qui ne veut pas dire que l'instruction est invalide
simplement, considéré comme n'utilisant pas la mémoire
sinon, ça ferait un plantage
donc, voici l'équivalent
ce qui mettait en tord tous les désassembleurs
jusque récemment
MOVSXD est une instruction 64b, qui étend un registre
donc, d'un registre vers un plus gros
mais sans préfixe REX - ce qui n'est pas encouragé,
on peut l'utiliser comme un MOV standard, de 32b vers 32b
et dans l'autre sens,
MOV d'un selecteur à un registre 32b marche sans problème,
alors que beaucoup de désassembleurs afficheraient MOV AX, CS, ce qui semble logique
niveau taille
mais en fait le mot de poid fort du registre cible est 'non défini'
mais ici, pas de surprise amusante,
ce sont juste des zéros
donc c'est équivalent à MOV EAX, CS
BSWAP est un de mes favoris
parce que je le compare à une administration:
il est supposé passer d'un endianisme [endianness] à l' autre
mais pour diverses raisons,
il ne peut jamais faire son travail correctement
donc, c'est juste en 64b que tout se passe
comme prévu
en 32b, comme toutes les autres instruction 32b
le double mot de poid fort est mis à zéro...
et sur un mot, il est non défini officiellement,
mais il est pourtant utilisé couramment dans les virus et les packeurs
car il remet simplement à zéro le registre,
comme un simple XOR AX, AX
donc, devant un résultat si inexplicable, je comprends
qu'Intel ne veuille pas en parler
ça serait sûrement gênant de devoir expliquer
ce résultat plutôt comique.
BSWAP AX est aussi mal géré par WinDbg et les autres
on lira BSWAP EAX
alors que le registre est remis à zéro... génant
tout le monde comprend ce code?
quelqu'un voit un piège possible?
l'adresse de est empilée
puis dépilée par RETN
donc, on saute vers une adresse immédiates
l'ordre d'exécution ?
oui, l'exécution démarre ici
non, rien à voir ici
voilà OllyDbg 1 - c'est corrigé dans le 2
Olly essaie même d'être sympa et de vous dire
via un commentaire automatique,
que RET sera utilisé comme saut vers
et, voyez le résultat, plutôt différent
donc, que c'est-il passé ?
quelqu'un voit ?
donc, RETN a un prefix 66
donc, il retourne vers IP (16b), pas EIP
donc on ne saute pas vers 401008, mais 00001008
et dans mon exemple, la page nulle a été allouée
et du code a été placé à 1008
donc, ça ne retourne pas vers []
mais l'autre problème est que c'est aussi appelé un RETN
bien que ce soit différent. les désassembleurs ont leur propre façon de l'afficher
tel que 'small retn', 'ret.16', ou autre
mais officiellement, c'est la même instruction qu'un RETN standard.
donc, le dernier Hiew, et OllyDbg 1
peut-être pas le 2
mais on peut se faire avoir
et le préfixe 66 a le même role avec CALLs, RETs, LOOPs, JMPs
toutes les instructions contrôlant le flux d'exécution
je ne vais pas tout énumérer
car sinon vous allez mourir d'ennui
pour en savoir plus, j'ai créé une page sur Corkami [x86.corkami.com],
avec quelques graphiques et penses-bêtes
pour faciliter le boulot
bon, trop de théorie
je n'aime pas lire sans avoir quelque chose à me mettre sous le débogueur,
donc j'ai créé CoST
ce qui signifie Corkami Standard Test
CoST est un unique binaire, sans option de ligne de commande
on l'exécute, il fait plein de tests
le tout dans un PE compliqué
pour aussi tester vos outils PE
ou vos connaissances
mais, dans ce PE compliqué, il est casse-pied à déboguer
donc j'ai fait aussi une version 'PE standard'
si on ne veut étudier que l'assembleur
sans difficultés
donc, CoST contient de nombreux tests
les classiques, triviaux,
un peu plus compliqués, JMP to IP, IRET...
les non documentés
les spécifiques aux processeurs, comme MOVBE, POPCNT, CRC32
les détections d'OS et VM
comme le fameux 'red pill'... juste une instruction SLDT, on compare le résultat,
et on baptise ça 'red pill'... sans commentaire...
et aussi, des bogues des SE, parce que Windows XP
se trompe en essayant de vous dire
quelle exception vient de se produire,
ce qui permettrait de différencier un SE réel d'un émulateur.
CoST est écrit en assembleur, donc rien de superflu
pas compilé, ni généré
mais pour le documenter un peu plus, j'ai ajouté des exports internes
donc on peut aller facilement d'une section à l'autre
donc on se retrouve facilement à la partie qui nous intéresse
via les exports,
et je voulais pouvoir afficher un message de manière pratique
sans que ça allonge trop le listing
je veux dire, devoir faire défiler l'écran parce qu'il est couvert d'appel à printf
donc j'au utilisé un Vectored Exception Handler, et une instruction-marqueur
donc plein de commentaires sont affichés,
directement dans le code
donc ça fait un code binaire documenté sans fichier de symboles de débogage
et vous avez vu, il n'y a pas beaucoup d'affichage
mais en fait, il y a beaucoup plus d'affichage de déboguage
une centaine au moins, qui indiquent même ce qui va se produire, etc
donc on est pas pris au dépourvu
on est guidé lors de l'analyse
quelqu'un comprend de quoi il s'agit?
c'est un de mes préférés
on ne peut pas voir les opcodes
ah, il n'y a pas d'astuce à ce niveau là cette fois-ci ;)
donc, on empile des valeurs
on saute ici
et avec le RETF, j'ai empilé l'adresse de 'push_eip'
et un mot, 0x33
donc on va revenir de loin ici
donc on retourne à cette adresse avec un sélecteur 33
qui est réservé au mode 64b, même pour un programme en 32b
donc on va revenir ici, en mode 64b
car 33 est le sélecteur pour le mode 64b
qu'on peut atteindre depuis un programme 32b
donc le code sera d'abord exécuté en mode 32b, avec le sélecteur standard,
puis à nouveau, avec le sélecteur 33
en mode 64b
donc on a la même adresse, les mêmes opcodes,
mais le désassemblage sera différent
et j'ai choisi des opcodes qui génèrent des instructions
spécifique à chaque mode
donc, c'est déjà une belle s*loperie à désassembler,
car avec la même EIP, si on ne fait pas attention au sélecteur,
on est bloqué
On peut déboguer un tel code - voir ma présentation à BerlinSides, transparent de démonstration 58
Si on l'exécute en passant par dessus, on retourne au sélecteur d'origine
qui était sauvé par le PUSH CS
donc on retourne à avec le sélecteur initial
l'exécution est rapide,
mais difficile à déboguer (uniquement avec WinDbg+wow64exts)
ça rend inutile les désassembleurs, et la plupart des débogueurs
pourtant, c'est si simple.
Voilà ce que donne CoST
sous la dernière version de Hiew
je pense que ce sera bientôt corrigé
c'est un HINT NOP non documenté par Intel
et oublié par la plupart des désassembleurs
donc WinDbg et Hiew
vous donnent des points d'interrogations
au début, je pensais m'arrêter là pour Hashdays
mais j'ai décidé de rajouter quelques astuces PE à CoST
donc, en voici l'en-tête... MZ, puis du texte
donc on peut taper 'type cost.exe', comme ce bon vieux Budokan...
et ensuite,
l'en-tête 'NT headers' - appelé 'PE' car il commence par ces lettres-là,
et en fait à la fin du fichier
le 'pied-de-page PE'
et les valeurs de l'en-tête sont un peu extrême
donc, pour le moins inattendu
donc voici ce qu'IDA 6.1 en pensait
...plantage directe...
ça lui apprendra à se fier aux valeurs
mais, aussi bien dans CoST, on peut changer un registre, faire une comparaison, tester,
et enchaîner ainsi plein de tests,
aussi bien, côté PE, on n'a qu'un seul binaire, donc
un seul essai
donc même si CoST n'a pas de section, des TLS bizarres,....
il ne peut pas tout tester
donc, j'ai créé pour ça une autre page sur Corkami
avec comme d'habitudes, des exemples, des graphiques,
elle n'est pas finie, mais déjà largement suffisante pour tester ou faire planter
n'importe quel outil
j'ai déjà une centaine d'exemples,
qui mettent l'accent sur de nombreux aspects, avec parfois des résultats inattendus
donc, voici une table de section virtuelle, et Hiew
quand les alignements sont bas, on n'a pas besoin de section
ou la table peut être vide
donc, j'ai modifié SizeOfOptionalHeader pour qu'il pointe en espace mémoire virtuel
donc la table des sections est en dehors du PE, pleines de zéros
et Hiew n'aime pas ça. En conséquence, il ne pense même pas que c'est un PE
alors que ça marche parfaitement, du moins sous XP
car Windows 7 est plus capricieux concernant les valeurs de la table des section
ensuite...
si on fait de l'art dans les Data Directories
vous pouvez commencer à vous inquiéter
si vous avez un dessin plus joli, je suis preneur
donc, il s'agit du 'Dual PE header', présenté
par Reversing Labs à la BlackHat
quelqu'un connaît ?
donc, on augmente SizeOfHeader pour que
les en-têtes NT soient en fait assez loin,
pour qu'il soit aligné avec les sections
quand il se charge en mémoire
la première section sera chargée par dessus
la première partie du OPTIONAL_HEADER est lue dans le fichier
donc, prise en compte pour charger le fichier
mais les Data Directories sont lus en mémoire
donc, d'abord l'OPTIONAL_HEADER est analysé, chargé en mémoire,
puis la section est dépliée sur la partie basse de l'en-tête
et les vrais Data directories qui étaient initialement au début de la section
vont être pris en compte,
donc tout ceci est du pipeau présent dans le fichier, à la suite de SizeOfOptionalHeader
mais en mémoire, ce sera écrasé et ignoré
une autre bizarrerie est que les noms d'exports peuvent avoir n'importe quelle valeur,
jusqu'au caractère zéro
donc, absolument n'importe quoi,
et de plus,
Hiew les affichent directement
donc on peut insérer ses propres messages
ce sont juste des noms d'exports, d'ailleurs
l'un d'entre eux est excessivement long,
idéal pour tester les dépassement de tampon [buffer overflow]
dans votre outil favori
et il est également possible d'avoir un export avec un nom nul,
on peut donc importer l'api nulle
sans problème
j'ai aussi essayé les différentes possibilités,
avec des fichiers contenant un maximum de section,
la limite est 96 sous XP, et 64K sous Vista et Windows 7
ce qui donne
avec le dernier OllyDbg 2 ce message surprenant
mais le fichier charge quand même
OllyDbg 1, lui, plante directement
il reste du temps ?
un dernier... pas très visuel
l'adresse AddressOfIndex du TLS est mise à zéro au chargement
et le terminus [terminator] des imports n'a pas besoin d'être composé de 5 doubles mots nuls
mais juste d'un seul, pour son AddressOfName
pour être considéré comme le terminus
donc, si on fait pointer AddressOfIndex vers l'AddressOfName d'un descripteur d'imports
s'il est mis à zéro,
les imports seront tronqués
et en fait, le comportement est différent sous XP ou 7
donc, sous XP, il est écrasé après que les imports soient chargés,
donc la table n'est pas tronquée
alors que sous 7, il est écrasé avant les imports
donc, pour le même PE, on a 2 comportements différents
sous deux versions de Windows différentes,
alors que le fichier marche sous les 2 versions.
ah, attendez, on a encore le temps?
15 minutes ? ok
je vais commencer la démonstration
pour vous montrer...
le style de PE que je créé d'habitude
avec le minimum d'éléments définis
c'est en fait un pilotes [driver]
même si j'ai utilisé des opcodes non documentés,
ce pilote marche, sans le vrac
habituel rajouté par le compilateurs
donc, un exemple clair, simple
sans rien pour vous géner la vue
ou votre débogueur
donc, cet exemple regarde les valeurs de CR0
via SMSW, officiellement non défini sur un double mot
mais en fait donne la même valeur
que le MOV EAX, CR0 standard
ensuite, MOV EAX, CR0 avec un 'mauvais' Mod/RM
sous le dernier Hiew, ce n'est en fait même pas désassemblé
on espère que ça ne plante pas...
donc, vous voyez, on obtient les mêmes valeurs
via le CR0 standard, l'invalide, et le non défini
dont la partie de poids fort est non définie
d'habitude, en langage Intel, non défini signifie 'mis à zéro',
mais ici, on a bien CR0 entier
et ma machine n'a même pas plantée
ce qui signifie que le pilote fonctionne correctement
donc vous pouvez étudier des petits pilotes
le premier exemple présenté aujourd'hui
était celui avec l'assembleur ancien
quelqu'un connaît le résultat final ?
certaines instructions sont inutiles,
juste pour vérifier que le processeur les gère
mais d'autres modifient les registres
et ces instructions des années 70-80
sont toujours gérées par les processeurs modernes
un des exemples que j'ai crée teste
les valeurs initiales de chaque registre
donc on peut voir les valeurs possibles sous XP ou W7
à chaque fois [TLS, EntryPoint, DllMain], je sauve tous les registres
et je compare à diverses valeurs possibles
successivement
en fait, au TLS, on a beaucoup de contrôles de ces valeurs,
car ces valeurs proviennent du Data Directory
en particulier, son adresse relative, sa taille, les callbacks...
pour plus de détail, voir le source...
ça vous permet d'imiter mieux un SE dans votre émulateur,
si ça vous intéresse
on utilise SMSW, on compare la valeur
ensuite, après une opération FPU, on regarde si la valeur a changé
et si elle revient à sa valeur initiale.
une autre bizarrerie, si quelqu'un a l'explication
est qu'en fait
ça se comporte différemment si on exécute le fichier normalement
ou avec une redirection
si on redirige, 'échec'
sinon, ça marche normalement.
pour vous montrer... on exécute, et on lance TYPE
normal : OK
redirection: ECHEC
si vous avez une explication, je suis preneur
tu as essayé de rediriger vers autre chose ?
non, je n'ai pas essayé
donc, rediriger vers un autre périphérique?
mais, comment on récupère le résultat ?
imprimante ?
je n'ai pas de périphérique COM
non, je ne sais pas
mais c'était surprenant, car je lançais tous mes tests...
et d'un coup, 'ECHEC'...
alors qu'à la main, aucun problème.
l'astuce avec GS
très simple
et quelques affichages
je modifie GS, qui se remet à zéro
puis j'attends le résultat,
ensuite je fais 2 tests et je compare le temps entre
car ça ne doit pas arriver trop vite
NOPs
je teste les NOPs non documentés
celui sur une page invalide
NOP standard
32 bits
tous mes tests 64b sont fait dans des programmes 32b, car on peut les exécuter sur un OS normal
et ensuite je regarde GS pour voir si le mode 64b est disponible
dans ce cas, on obtiendrait un résultat différent
donc, en 64b, que je n'ai aps ici, on obtiendrait
les tests en 64b
et ces results.
mais, pas si facile à déboguer
mais ici, pas de piège, donc on peut revenir facilement en 32b
on saute le code 64b et revient en 32b
PUSH/RET
on affiche le message et ensuite...
Olly vous dit qu'on va sauter vers [4010]08
mais en fait - ici, c'est correct
et le TLS a alloué la page NULLE
qui affiche 'ECHEC'
donc, comme mentionné auparavant, pas de façon standard de désassembler ça correctement
je ne peux pas exécuter les 64K sections
et en fait, cet exemple exécute tout le code (l'espace virtuel complet des 65535 sections)
elles sont grosses,
et je modifie EAX pour que tous les 00 00 soient exécutés
juste pour faire un printf à la fin
ça prend plusieurs secondes sur un i7
c'est assez amusant: on le lance, et même avec le cache,
et que le SE n'est pas occupé, ça prend un temps visible, juste pour un tas de 00
les sections virtuelles... celui que le dernier Hiew ne voit pas comme un PE
enfin, bientôt corrigé
ah, je ne peux pas l'analyser car il ne pense même pas que c'est un PE
mais, pour simplifier, OPTIONAL_HEADER pointe au delà
du fichier
l'en-tête plié...
quelques messages d'erreurs,
à cause des faux Data Directories
et les DD réels sont au début
de la première section
ceci deviendra les imports, et le vrai Data Directory
et pour finir, celui avec TLS AddressOfIndex qui pointe...
...dans les descripteurs d'imports, sur AddressOfName
donc, il sera écrasé au chargement
et quand on le charge, il reconnait XP
à la façon dont les imports ont été chargés
et sous 7, on obtiendra un autre résultat.
pour finir, les exports
certains ont des noms très longs
en fait, on remarque que j'écrase le désassemblage même
donc je répète les faux opcodes et adresses
donc le désassembleur est perturbé
mais c'est juste visuel, pas vraiment grave
bien que ce soit un problème récent dans IDA
ou si on met un export au milieu d'une instruction
l'export aura priorité sur le désassemblage,
et casse l'instruction en deux
il y a bien sûr un exemple pour ça sur Corkami
donc, voilà pour les démonstrations
Je voulais donc en savoir plus sur le x86 et le PE
qui sont loin d'être correctement documentés
et qui ne le sont toujours pas,
mais j'en ai couvert un peu
il y a encore des flous
mais de moins en moins, j'y travaille
en publiant mes recherches librement
tel WinDbg, si vous suivez les documentations officielles à la lettre,
vous êtes voués à l'échec, surtout avec tous les virus et packeurs existants
si vous êtes intéressés, ou que vous programmez un outil, un émulateur, un moteur...
vous savez que vous pouvez aller sur Corkami, lire les pages
télécharger les exemples, qui sont disponibles librement, à tout point de vue
et si vous trouvez des bugs, ce qui pourrait arriver
envoyez-moi une carte postale, ou un t-shirt croix rouge ;)
Merci à Peter Ferrie, et tous mes relecteurs et contributeurs
vous avez des questions ?
tu les as testés sur des anti-virus ? tu devrais trouver une ch*ée de 0days
mouai, non, je ne saurais pas en faire des exploits...
tenir en échec les désassembleurs me suffit
j'ai trouvé un crash dans Xed d'Intel, ça me suffit
une autre question ? tout le monde a survécu ?
c'était une super présentation, mec
merci!
MERCI!