Mot-clé - scripts

Fil des billets - Fil des commentaires

dimanche, juillet 8 2012

Packons (ou pas) avec miasm et elfesteem

Comme beaucoup le savent à présent MIASM est un framework d'ingénierie inverse écrit en Python par Fabrice Desclaux. Pour ma part j'avais joué un petit peu avec il y a un an, mais j'étais finalement assez rapidement passé à autre chose devant mon incapacité à porter un "packeur" maison de pefile jusqu'à miasm. Aujourd'hui, je re-tente la même tâche !

Engine - Creative Common by "cbowns" on Flickr
Il y a un an donc j'avais eu des soucis quand j'avais voulu porter un concept simple de packeur sur le framework miasm. En effet la base de tout packeur c'est d'ouvrir un fichier à traiter (dans mon cas un fichier PE), lui apporter des modifications, puis le sauvegarder pour obtenir un clone fonctionnel de notre fichier à traiter (clone dont la signature binaire sera pourtant différente). Le problème c'est que, même en n'apportant aucune modification à mon fichier cible, elfesteem semblait incapable de générer un clone fonctionnel. Par exemple le code suivant, qui est censé n'apporter aucune modification à l'exécutable passé en argument, me retournait systématiquement un clone que Windows refusait de démarrer :

import sys
from elfesteem.pe_init import PE

e = PE( open(sys.argv[1]).read() )

open( sys.argv[1]+'_modified.exe', 'wb').write(str(e))

De mémoire j'avais testé sur "winmine.exe", sur "calc.exe", et sur "notepad.exe" : à chaque fois l'exécutable modifié ne fonctionnait plus :-(

Un an après, re-motivé par la super conf de serpi au SSTIC, je réinstalle smiasm sur ma machine[1] et je re-tente. Tristesse : le bilan est le même. Les exécutables que j'ouvre puis que je sauvegarde avec elfesteem ne fonctionnent plus (enfin en tout cas "calc.exe", qui est ma cible de test préférée, donne systématiquement des clones "cassés"). Mais cette année, j'ai décidé d'investiguer un petit peu plus ! Retroussons nous les manches et voyons voir ça de plus près. D'abord assurons nous que "calc.exe" et "calc.exe_modified.exe" sont bien différents (et que nous n'avons donc pas à faire à un simple saute d'humeur de windows) : diff calc.exe calc.exe_modified.exe nous confirme que les fichiers sont différents.

Pour y voir de plus près on va regarder ces deux spécimens en hexa (xxd calc.exe > calc.exe.XXD && xxd calc.exe_modified.exe > calc.modified.XXD && diff *.XXD) :

5,14c5,14
< 0000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468  ........!..L.!Th
< 0000050: 6973 2070 726f 6772 616d 2063 616e 6e6f  is program canno
< 0000060: 7420 6265 2072 756e 2069 6e20 444f 5320  t be run in DOS 
< 0000070: 6d6f 6465 2e0d 0d0a 2400 0000 0000 0000  mode....$.......
< 0000080: 8745 1664 c324 7837 c324 7837 c324 7837  .E.d.$x7.$x7.$x7
< 0000090: 3907 3837 c624 7837 1907 6437 c824 7837  9.87.$x7..d7.$x7
< 00000a0: c324 7837 c224 7837 c324 7937 4424 7837  .$x7.$x7.$y7D$x7
< 00000b0: 3907 6137 ce24 7837 5407 3d37 c224 7837  9.a7.$x7T.=7.$x7
< 00000c0: 1907 6537 df24 7837 3907 4537 c224 7837  ..e7.$x79.E7.$x7
< 00000d0: 5269 6368 c324 7837 0000 0000 0000 0000  Rich.$x7........
---
> 0000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
21c21
< 0000140: 00f0 0100 0004 0000 fcd7 0100 0200 0080  ................
---
> 0000140: 00f0 0100 0004 0000 b16f 0200 0200 0080  .........o......
39,46c39,46
< 0000260: 0ffe 7d3b 3800 0000 0efe 7d3b 4400 0000  ..};8.....};D...
< 0000270: 0efe 7d3b 4f00 0000 0efe 7d3b 5c00 0000  ..};O.....};\...
< 0000280: 0efe 7d3b 6900 0000 0efe 7d3b 7300 0000  ..};i.....};s...
< 0000290: 0000 0000 0000 0000 5348 454c 4c33 322e  ........SHELL32.
< 00002a0: 646c 6c00 6d73 7663 7274 2e64 6c6c 0041  dll.msvcrt.dll.A
< 00002b0: 4456 4150 4933 322e 646c 6c00 4b45 524e  DVAPI32.dll.KERN
< 00002c0: 454c 3332 2e64 6c6c 0047 4449 3332 2e64  EL32.dll.GDI32.d
< 00002d0: 6c6c 0055 5345 5233 322e 646c 6c00 0000  ll.USER32.dll...
---
> 0000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000270: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000290: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

Voilà donc l'intégralité des changements entre les deux fichiers. Sur 112ko ça fait peu, on devrait pouvoir être capable d'identifier précisément ce qui ne fonctionne plus :)

Le premier bloc de différence se situe entre les octets 0x0000040 et 0x00000d0. La position (en tout début de fichier) et le contenu ("...This program cannot be run in DOS mode...") de ce bloc me font penser qu'il s'agit uniquement du "DOS stub"[2]. Bref : pas intéressant.

Le second bloc de différence est beaucoup plus petit puisqu'il n'impacte que la ligne de notre dump commençant à l'octet 0x0000140. Sur toute cette ligne on a 3 octets de différence. Si jamais ces octets participent à une indirection quelconque on a peut-être trouvé notre coupable :) ! Après un petit tour dans "Lord PE" je réalise que, malheureusement, ce n'est probablement pas là que se situe le coupable. En effet la valeur du checksum de "calc.exe" est "0001D7FC" et celle de "calc.exe_modified.exe" est de "00026FB1". Cette suite d'octets correspond donc probablement au checksum du fichier contenu dans l'en-tête NT_HEADER. Comme le DOS-stub a été supprimé il est normal que la valeur de checksum soit différente; de plus il me semble qu'un checksum erroné n'empèche pas le lancement de programme simples (pour les drivers c'est une autre histoire, mais pour un soft user-land je ne crois pas que Windows vérifie). Pour avoir la conscience tranquille je modifie quand même la valeur du checksum dans "calc.exe.XXD" puis je reconstruit un binaire (xxd -r calc.exe.XXD > calc.exe.badchecksum.exe) et je peux confirmer que cette copie de calc.exe dont j'ai uniquement modifié le checksum pour en mettre un invalide fonctionne :)

Nous arrivons ainsi au troisième bloc, qui est devenu notre suspect numéro 1. En plus, les quelques chaines de caractères qui ont été remplacées par des octets nuls ont bien la tête d'informations provenant d'une table d'importation ("msvcrt.dll" , "KERNEL3.dll", etc.) ce qui pourrait expliquer le non-fonctionnement du programme si ces informations ont été altérées. Malheureusemnt "Lord PE" me donne des tables d'importations identiques entre les deux fichiers "calc.exe" et "calc.exe_modified.exe", et je ne trouve pas d'outil sympa pour disséquer simplement un PE :-( ...Qu'à celà ne tienne : on va en pondre un maison, à la porc bien entendu ! L'idée est simple : visualiser les structures du fichier PE simplement afin d'identifier à quelle structure appartient la zone d'octets modifiés.

Pour réaliser cet outil, quick&dirty rappelons-le, je vais faire appel à une poignée de fidèles compagnons :

  • VIM/gedit : parce que ce sont d'excellents éditeurs de code :)
  • C : parce que pour les manipulation "bas niveau" on n'a pas encore fait mieux que le bon vieux C.
  • Python : parce que pour les prototypages rapide c'est le must.
  • Python Imaging Library (PIL) : parce qu'on veut "visualiser les structures du fichier PE" et que c'est la meilleure librairie de manipulation d'image en python que je connaisse.

Enfin, en guise d'aide mémoire, on utilisera aussi les excellents tutos d'Iczelions sur les entrailles du format PE et quelques URL de références de chez Krosoft. Au final j'ai obtenu en un aprem de jeu :

  • un prog en C qui analyse un fichier PE et dump ses caractéristiques sur la sortie standard (en prime j'ai rajouté un .h contenant les définitions des structures Windows, bien que j'aurai pu utiliser winnt.h qui doit se trouver quelque part sur ma machine étant donné que j'ai une gentoo avec de quoi compiler pour des centaines de systèmes exotiques, mais j'avais la flemme de le chercher :-D)
  • un prog en python qui lance le prog en C sur un exécutable passé en argument, parse la sortie obtenue, et génère deux jolies images permettant de visualiser quelle partie du fichier appartient à quelle structure.

Toutes les sources sont en pièce-jointe de ce billet, donc n'hésitez pas à jouez vous aussi (mais n'oubliez pas : j'ai codé ça comme un goret donc si ça ne marche pas chez vous "c'est normal", et si vous le lancez sur des PE de plusieurs Mo il est probable que votre RAM soit intégralement vampirisée, vous êtes prévenus).

La première image est une représentation à l'échelle des différentes structures du fichier PE sur disque (pas tel qu'il sera mappé en mémoire donc). La seconde image n'est qu'une représentation "compressée" de l'image précédente où j'ai inséré des séries de "petits points" pour remplacer visuellement les énormes applats de couleurs unis représentant des zones de fichier où "rien de spécial ne se passe" et qui rendent le premier type d'image carrément gigantesque (1200x14688 pour représenter "calc.exe" dans mon environnement de test ;) ). Voilà comment on s'en sert :

$ gcc -o a.out oz_pedissector.c
$ python oz_pedissector3.py calc.exe
INFO:root:PE zone discovered [ZONE : IMAGE_DOS_HEADER	En-tete DOS	0	64]
INFO:root:PE zone discovered [ZONE : IMAGE_NT_HEADERS	En-tete NT	240	488]
INFO:root:PE zone discovered [ZONE : SECTION_HEADERS[0]	En tete de section ".text"	488	528]
INFO:root:PE zone discovered [ZONE : SECTION[0]	Contenu de section ".text"	1024	76800]
(...)
INFO:root:Compression second pass 98%
INFO:root:Compression second pass 99%
INFO:root:Compression second pass finished.

Nous voilà à présent avec deux fichiers ".png", l'un étant la représentation à l'échelle, l'autre étant la représentation compressée. Voyons voir ce que donne l'image "compressée" générée automatiquement à partir du "calc.exe" de mon environnement de test (je vous invite à l'avoir sous les yeux en taille normale lorsque vous lirez le paragraphe suivant) :

Oz PE dissector (Compressed Example file) - Creative Common CC-BY-SA by Ozwald

En la regardant de haut en bas on retrouve bien :

  • L'en-tête MS-DOS commençant à l'adresse 0x0 et allant jusqu'à 0x40
  • Une zone noire (représentation graphique de "on ne sait pas ce que c'est") juste en dessous. C'est le DOS Stub.
  • l'en-tête NT commençant à 0xF0 et allant jusqu'à 0x1E8 (on se rappelle à ce moment là que le second bloc de différence était situé aux alentours de l'adresse 0x140 et que nous avions supposé qu'il s'agissait du checksum contenu dans l'en-tête NT, nous avons à présent confirmation que cette adresse est bien dans l'en-tête NT ce qui conforte notre hypothèse s'il en était besoin)
  • Les trois en-têtes de sections qui suivent l'en-tête NT
  • Une seconde zone noir juste en dessous...on en reparlera.
  • La première section (".text") qui va de 0x400 à 0x12C0. On notera (sur la droite de l'image) que cette section contient les zones mémoires suivantes appartenant au DATA_DIRECTORY : Import Address Table, Debug, Import Symbols.
  • La seconde section (".data") qui ne contient rien de spécial.
  • La troisième section (".rsrc") qui contient, on s'en doutai, la zone spéciale "Resources" du DATA_DIRECTORY.

Bref : tout va bien :) Tout va tellement bien d'ailleurs que je tombe d'accord avec elfesteem : il ne devrait rien y avoir d'important entre l'adresse 0x260 et 0x400[3]...Le mystère reste donc entier[4].

Edit du lundi 9 Juillet 2012, 14h53 : je remplace les 3 fichiers sources joints au billet par une archive unique contenant le tout, ça évitera les 403 imposés par mon hébergeurs sur les fichiers d'extension ".py" -_-

Edit du mardi 10 Juillet 2012, 17h30 : après vérification sur d'autres versions de "calc.exe" (provenant de diverses versions de WinXP) on retrouve toujours ce bloc non nul entre la fin des en-tête de section et le début de la première section... Etrange... Par contre je viens seulement maintenant de vérifier un exécutable qui serait plus typiquement ce que j'aurai besoin de packer[5], à savoir gsecdump, et il ne contient bien que des octets nuls entre la fin de ses en-tête de section et le début de sa première section. Donc à priori il devrait pouvoir être ouvert/enregistré par elfesteem sans être "cassé". Je teste ce soir :) ! Accessoirement je rajoute en PJ la version 4 du script dégueu programme permettant la visualisation des structures de fichiers PE. Il est maintenant infiniment moins gourmand en mémoire (forcément: il génère du svg directement compressé au lieu de générer un bitmap "taille réelle" puis de le compresser...); je l'ai testé sans douleur sur un binaire de 13Mo (en lui demandant cependant de n'afficher que les structures de plus de 40 octets).

Edit du mardi 10 Juillet 2012, 20h30 : A force d'avoir l'évidence sous les yeux (à savoir que je ne vois aucune raison pour lesquelles elfesteem ne pourrait pas légitimement écrire des bytes nuls sur ces zones du fichier) j'ai re-testé l'ouverture/sauvegarde simple mais sur plusieurs binaires ne provenant pas de Microsoft cette fois (à savoir : gsecdump-v2b5.exe, ftpserv.exe, notepad++.exe, 7zFM.exe, python.exe) et ils ont tous fonctionné à merveille après leur passage dans elfesteem. La conclusion du premier chapitre de ce mystère s'écrit donc d'elle même : elfesteem fonctionne très bien, c'est le compilateur utilisé par microsoft qui produit des résultats "exotiques" (et je suis un gros c*uillon de ne pas m'en être rendu compte il y a un an -_- ...).

Edit du mercredi 11 Juillet 2012 : Serpi a trouvé la solution (lisez les commentaires du billet pour les détails). Le problème venait de bound imports..

Notes

[1] L'installation n'est d'ailleurs toujours pas plus facile, je vous recommande la lecture de mon billet de l'époque, ça peut éventuellement aider

[2] Le DOS stub est,en résumé, un programme MSDOS censé être lancé à la place du programme PE lorsqu'il est exécuté sous DOS. Le but de ce mini programme DOS est principalement d'avertir l'utilisateur que le programme ne peut pas fonctionner sous DOS.C'est une zone totalement facultative de nos jours.

[3] Ce genre de zone "poubelle" pouvant apparaitre naturellement dans le fichier suite à des jeux de padding puisque les structures PE imposent des tailles multiples de certaines constantes renseignées dans les en-tête

[4] En attendant un autre après-midi de libre (où je pourrait me re-pencher sur le problème), ou que quelqu'un poste un commentaire expliquant ce bien étrange phénomène

[5] Pour rappel : je suis pentesteur pro, si je pack des binaires c'est pour mon taff et pour sensibiliser mes clients au fait qu'un AV ce n'est pas l'outil miracle qui les protègera de tout et n'importe quoi; ce n'est pas pour faire du pr0n, monter un botnet, ou pire : boire dans votre YOP.

samedi, octobre 22 2011

Brèves du jour

Un mini billet pour deux petites brèves : une astuce python (déjà bien connue, mais plus on réplique ce genre d'info moins on a de mal à la retrouver quand on l'a oublié), et un pointeur sur un jouet sympa.

Tools - Creative Common by mtneer_man on Flickr
Tout d'abord l'astuce python : comment changer son user-agent quand on utilise la librairie urllib. C'est un problème ultra-classique puisque pas mal de sites font du filtrage sur ce user-agent et refusent de répondre quand ils repèrent un script python[1]. En fouillant sur le net on trouve pleins de méthodes plus ou moins bidons (certaines se contentent carrément de rajouter un second header "user-agent", ce qui n'est évidemment pas ce que l'on cherche à faire), et après en avoir testé au moins une demi-douzaine voici celle que j'utilise à présent (et qui marche :D) :

import urllib
# ============ USER AGENT MAGIC ===========
class UAOpener(urllib.FancyURLopener):
        version = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11'
urllib._urlopener = UAOpener()
# =========================================

Et voilà :) Comme pour le passage via TOR c'est tout ce qu'il y a à faire. Accessoirement j'ai réalisé récemment que le dernier paramètre de "setdefaultproxy" que j'utilise pour passer par TOR souffre un bug ennuyeux : il est ignoré :-D Donc que vous mettiez à "True" ou à "False" les résolutions DNS ne passeront JAMAIS par votre proxy socks (et donc par TOR)...ennuyeux n'est-ce-pas ? En tout cas maintenant vous êtes prévenus :)

Le petit jouet maintenant : ça n'a rien de nouveau non plus, mais c'est tout de même un jouet rigolo. Il s'agit du WOrdnet Libre du Français[2]. Pour ceux qui connaissent Wordnet : c'est Wordnet pour le Français ;) Pour ceux qui ignorent ce qu'est Wordnet c'est une base de donnée concernant le sens des mots et leurs relations les uns avec les autres. Un usage immédiat de Wordnet c'est de trouver des synonymes (dans la langue française avec WOLF, dans la langue anglaise avec le Wordnet original), un autre usage consiste à mesurer la proximité de sens entre deux mots par exemple. Les possibilités sont vraiment nombreuses, notamment en data-mining. Bref vous pouvez le télécharger ici au format XML, et ces trois mini remarques pourraient vous êtes utiles si vous voulez travailler avec :

  • Le document ne contient pas de balise racine, ajoutez donc en une si vous voulez que xmllint --format ou xml.dom.minidom.Parse ne vous insulte pas. Par exemple un petit <wolf> en début de document et un petit </wolf> en fin de document feront parfaitement l'affaire.
  • Les '&' ont tendance à faire crier le module python xml.dom.minidom. Pour ma part je les ai donc purement et simplement supprimés grâce à un petit sed.
  • Le XML pèse 38Mo sur disque, une fois parsé par xml.dom.minidom mon interpréteur Python prend plus de 1Go ;-) Veillez donc à avoir de la RAM disponible.

Notes

[1] Par exemple wikipedia.

[2] WOLF